/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ // // Public interface and shared subsystem data. // #ifdef EDITOR #include "editor.h" #include "libimg.h" #include "intl_csi.h" #include "xp_str.h" #ifdef USE_SCRIPT #define EDT_IS_SCRIPT(tf) (0 != (tf & (TF_SERVER | TF_SCRIPT | TF_STYLE))) #else #define EDT_IS_SCRIPT(tf) (FALSE) #endif //----------------------------------------------------------------------------- // CEditElement //----------------------------------------------------------------------------- // // This version of the constructor is used to create a child element. // CEditElement::CEditElement(CEditElement *pParent, TagType tagType, char* pData): m_tagType(tagType), m_pParent(pParent), m_pNext(0), m_pChild(0), m_pTagData(0) { if ( pData ) { SetTagData(pData); } CommonConstructor(); } CEditElement::CEditElement(CEditElement *pParent, PA_Tag *pTag, int16 /*csid*/) : m_tagType(pTag->type), m_pParent(pParent), m_pNext(0), m_pChild(0), m_pTagData(0) { char *locked_buff; PA_LOCK(locked_buff, char *, pTag->data); if( locked_buff && *locked_buff != '>'){ SetTagData( locked_buff ); } PA_UNLOCK(pTag->data); CommonConstructor(); } void CEditElement::CommonConstructor(){ if( m_pParent ){ CEditElement* pE = m_pParent->GetChild(); // // there already is a child, add this child to the end of the list. // if( pE ){ while( pE->m_pNext != 0 ){ pE = pE->m_pNext; } pE->RawSetNextSibling(this); } else { // make this the first child. m_pParent->RawSetChild(this); } } } CEditElement::CEditElement( IStreamIn *pIn, CEditBuffer* /* pBuffer */ ): m_pParent(0), m_pNext(0), m_pChild(0), m_pTagData(0) { m_tagType = (TagType) pIn->ReadInt(); m_pTagData = pIn->ReadZString(); } CEditElement::~CEditElement(){ Finalize(); } void CEditElement::Finalize(){ Unlink(); DeleteChildren(); if( m_pTagData ) { XP_FREE(m_pTagData); m_pTagData = NULL; } } CEditTextElement* CEditElement::Text(){ XP_ASSERT(IsText()); return (CEditTextElement*)this; } XP_Bool CEditElement::IsLeaf() { return FALSE; } CEditLeafElement* CEditElement::Leaf(){ XP_ASSERT(IsLeaf()); return (CEditLeafElement*)this; } XP_Bool CEditElement::IsRoot() { return FALSE; } CEditRootDocElement* CEditElement::Root(){ XP_ASSERT(IsRoot()); return (CEditRootDocElement*)this; } XP_Bool CEditElement::IsContainer() { return FALSE; } CEditContainerElement* CEditElement::Container(){ XP_ASSERT(IsContainer()); return (CEditContainerElement*)this; } XP_Bool CEditElement::IsList() { return FALSE; } CEditListElement* CEditElement::List(){ XP_ASSERT(IsList()); return (CEditListElement*)this; } XP_Bool CEditElement::IsBreak() { return FALSE; } CEditBreakElement* CEditElement::Break(){ XP_ASSERT(IsBreak()); return (CEditBreakElement*)this; } XP_Bool CEditElement::CausesBreakBefore() { return FALSE;} XP_Bool CEditElement::CausesBreakAfter() { return FALSE;} XP_Bool CEditElement::AllowBothSidesOfGap() { return FALSE; } XP_Bool CEditElement::IsImage() { return FALSE; } CEditImageElement* CEditElement::Image(){ // Not all P_IMAGE elements are images. CEditIconElements have P_IMAGE // as their tagType but are not CEditImageElements. XP_ASSERT(m_tagType==P_IMAGE && GetElementType() == eImageElement && IsImage() ) ; return (CEditImageElement*)this; } XP_Bool CEditElement::IsIcon() { return FALSE; } CEditIconElement* CEditElement::Icon(){ XP_ASSERT(IsIcon()); return (CEditIconElement*)this;} XP_Bool CEditElement::IsTarget() { return FALSE; } CEditTargetElement* CEditElement::Target(){ XP_ASSERT(IsTarget()); return (CEditTargetElement*)this;} CEditHorizRuleElement* CEditElement::HorizRule(){ XP_ASSERT(m_tagType==P_HRULE); return (CEditHorizRuleElement*)this;} XP_Bool CEditElement::IsRootDoc() { return FALSE; } CEditRootDocElement* CEditElement::RootDoc() { XP_ASSERT(IsRootDoc()); return (CEditRootDocElement*) this; } XP_Bool CEditElement::IsSubDoc() { return FALSE; } CEditSubDocElement* CEditElement::SubDoc() { XP_ASSERT(IsSubDoc()); return (CEditSubDocElement*) this; } XP_Bool CEditElement::IsTable() { return FALSE; } CEditTableElement* CEditElement::Table() { XP_ASSERT(IsTable()); return (CEditTableElement*) this; } XP_Bool CEditElement::IsTableRow() { return FALSE; } CEditTableRowElement* CEditElement::TableRow() { XP_ASSERT(IsTableRow()); return (CEditTableRowElement*) this; } XP_Bool CEditElement::IsTableCell() { return FALSE; } CEditTableCellElement* CEditElement::TableCell() { XP_ASSERT(IsTableCell()); return (CEditTableCellElement*) this; } XP_Bool CEditElement::IsCaption() { return FALSE; } CEditCaptionElement* CEditElement::Caption() { XP_ASSERT(IsCaption()); return (CEditCaptionElement*) this; } XP_Bool CEditElement::IsText() { return FALSE; } XP_Bool CEditElement::IsLayer() { return FALSE; } CEditLayerElement* CEditElement::Layer() { XP_ASSERT(IsLayer()); return (CEditLayerElement*) this; } XP_Bool CEditElement::IsDivision() { return FALSE; } CEditDivisionElement* CEditElement::Division() { XP_ASSERT(IsDivision()); return (CEditDivisionElement*) this; } XP_Bool CEditElement::IsEndOfDocument() { return GetElementType() == eEndElement; } XP_Bool CEditElement::IsEndContainer() { return FALSE; } void CEditElement::SetTagData(char* tagData) { if( m_pTagData ) { XP_FREE(m_pTagData); } if( tagData ){ m_pTagData = XP_STRDUP(tagData); } else { m_pTagData = tagData; } } void CEditElement::StreamOut( IStreamOut *pOut ){ pOut->WriteInt( GetElementType() ); pOut->WriteInt( m_tagType ); pOut->WriteZString( m_pTagData ); } XP_Bool CEditElement::ShouldStreamSelf( CEditSelection& local, CEditSelection& selection) { return ( local.EqualRange( selection ) || ! local.Contains(selection)); } // Partially stream out this element and each child.except if we are told to stream all void CEditElement::PartialStreamOut( IStreamOut* pOut, CEditSelection& selection) { CEditSelection local; GetAll(local); #ifdef DEBUG TagType type = GetType(); #endif if ( local.Intersects(selection) ) { XP_Bool bWriteSelf = ShouldStreamSelf(local, selection); if( bWriteSelf ) { StreamOut(pOut); } CEditElement* pChild; for ( pChild = GetChild(); pChild; pChild = pChild->GetNextSibling() ) { pChild->PartialStreamOut(pOut, selection); } if ( bWriteSelf ) { pOut->WriteInt((int32)eElementNone); } } } XP_Bool CEditElement::ClipToMe(CEditSelection& selection, CEditSelection& local) { // Returns TRUE if selection intersects with "this". GetAll(local); return local.ClipTo(selection); } void CEditElement::GetAll(CEditSelection& selection) { CEditLeafElement* pFirstMostChild = CEditLeafElement::Cast(GetFirstMostChild()); if ( ! pFirstMostChild ) { XP_ASSERT(FALSE); return; } selection.m_start.m_pElement = GetFirstMostChild()->Leaf(); selection.m_start.m_iPos = 0; CEditLeafElement* pLast = GetLastMostChild()->Leaf(); CEditLeafElement* pNext = pLast->NextLeaf(); if ( pNext ) { selection.m_end.m_pElement = pNext; selection.m_end.m_iPos = 0; } else { // edge of document. Can't select any further. selection.m_end.m_pElement = pLast; selection.m_end.m_iPos = pLast->GetLen(); } selection.m_bFromStart = FALSE; } EEditElementType CEditElement::GetElementType() { return eElement; } // Get parent table of the element CEditTableElement* CEditElement::GetParentTable() { CEditElement *pElement = this; do { pElement = pElement->GetParent(); } while( pElement && !pElement->IsTable() ); return (CEditTableElement*)pElement; } // Get parent table cell of the element CEditTableCellElement* CEditElement::GetParentTableCell() { CEditElement *pElement = this; do { pElement = pElement->GetParent(); } while( pElement && !pElement->IsTableCell() ); return (CEditTableCellElement*)pElement; } // // static function calls the appropriate stream constructor // CEditElement* CEditElement::StreamCtor( IStreamIn *pIn, CEditBuffer *pBuffer ){ CEditElement* pResult = StreamCtorNoChildren(pIn, pBuffer); if ( pResult ) { pResult->StreamInChildren(pIn, pBuffer); } return pResult; } void CEditElement::StreamInChildren(IStreamIn* pIn, CEditBuffer* pBuffer){ CEditElement* pChild; while ( (pChild = CEditElement::StreamCtor(pIn, pBuffer)) != NULL ) { pChild->InsertAsLastChild(this); } } CEditElement* CEditElement::StreamCtorNoChildren( IStreamIn *pIn, CEditBuffer *pBuffer ){ EEditElementType eType = (EEditElementType) pIn->ReadInt(); switch( eType ){ case eElementNone: return 0; case eElement: return new CEditElement( pIn, pBuffer ); case eTextElement: return new CEditTextElement( pIn, pBuffer ); case eImageElement: return new CEditImageElement( pIn, pBuffer ); case eHorizRuleElement: return new CEditHorizRuleElement( pIn, pBuffer ); case eBreakElement: return new CEditBreakElement( pIn, pBuffer ); case eContainerElement: return new CEditContainerElement( pIn, pBuffer ); case eListElement: return new CEditListElement( pIn, pBuffer ); case eIconElement: return new CEditIconElement( pIn, pBuffer ); case eTargetElement: return new CEditTargetElement( pIn, pBuffer ); case eTableElement: return new CEditTableElement( pIn, pBuffer ); case eCaptionElement: return new CEditCaptionElement( pIn, pBuffer ); case eTableRowElement: return new CEditTableRowElement( pIn, pBuffer ); case eTableCellElement: return new CEditTableCellElement( pIn, pBuffer ); case eLayerElement: return new CEditLayerElement( pIn, pBuffer ); case eDivisionElement: return new CEditDivisionElement( pIn, pBuffer ); default: XP_ASSERT(0); } return 0; } // // Scan up the tree looking to see if we are within 'tagType'. If we stop and // we are not at the top of the tree, we found the tag we are looking for. // XP_Bool CEditElement::Within( int tagType ){ CEditElement* pParent = GetParent(); while( pParent && pParent->GetType() != tagType ){ pParent = pParent->GetParent(); } return (pParent != 0); } CEditBuffer* CEditElement::GetEditBuffer(){ CEditRootDocElement *pE = GetRootDoc(); if( pE ){ return pE->GetBuffer(); } else { return 0; } } XP_Bool CEditElement::InFormattedText(){ CEditElement* pParent = GetParent(); #ifdef USE_SCRIPT if( IsA( P_TEXT) && (Text()->m_tf & (TF_SERVER|TF_SCRIPT|TF_STYLE)) != 0 ){ return TRUE; } #endif while( pParent && BitSet( edt_setFormattedText, pParent->GetType() ) == 0 ){ pParent = pParent->GetParent(); } return (pParent != 0); } // // Fills in the data member of the tag, as well as the type informaiton. // void CEditElement::SetTagData( PA_Tag* pTag, char* pTagData){ PA_Block buff; char *locked_buff; int iLen; if ( NULL == pTagData ) { XP_ASSERT(FALSE); return; } pTag->type = m_tagType; pTag->edit_element = this; iLen = XP_STRLEN(pTagData); buff = (PA_Block)PA_ALLOC((iLen+1) * sizeof(char)); if (buff != NULL) { PA_LOCK(locked_buff, char *, buff); XP_BCOPY(pTagData, locked_buff, iLen); locked_buff[iLen] = '\0'; PA_UNLOCK(buff); } else { // LTNOTE: out of memory, should throw an exception return; } pTag->data = buff; pTag->data_len = iLen; pTag->next = NULL; return; } PA_Tag* CEditElement::TagOpen( int /* iEditOffset */ ){ PA_Tag *pTag = XP_NEW( PA_Tag ); XP_BZERO( pTag, sizeof( PA_Tag ) ); if( GetTagData() ){ SetTagData( pTag, GetTagData() ); } else { SetTagData( pTag, ">" ); } return pTag; } PA_Tag* CEditElement::TagEnd( ){ if( TagHasClose( m_tagType ) || BitSet( edt_setWriteEndTag, m_tagType ) ){ PA_Tag *pTag = XP_NEW( PA_Tag ); XP_BZERO( pTag, sizeof( PA_Tag ) ); pTag->type = m_tagType; pTag->is_end = TRUE; pTag->edit_element = this; return pTag; } return 0; } XP_Bool CEditElement::Reduce( CEditBuffer* /* pBuffer */ ){ if( !BitSet( edt_setSoloTags, GetType() ) && GetChild() == 0 ){ return TRUE; } else if( BitSet( edt_setCharFormat, GetType() ) ){ CEditElement *pNext = GetNextSibling(); if( pNext && pNext->GetType() == GetType() ){ // FONTs and Anchors need better compares than this. Merge(pNext); // make sure it stays in the tree so it dies a natural death (because // it has no children) if( pNext ) pNext->InsertAfter(this); return FALSE; } } return FALSE; } int CEditElement::GetDefaultFontSize(){ TagType t = GetType(); if( !BitSet( edt_setTextContainer, t ) ){ CEditElement* pCont = FindContainer(); if( pCont ){ t = pCont->GetType(); } else { return 0; // no default font size } } switch( t ){ case P_HEADER_1: return 6; case P_HEADER_2: return 5; case P_HEADER_3: return 4; case P_HEADER_4: return 3; case P_HEADER_5: return 2; case P_HEADER_6: return 1; default: return 3; } } CEditInsertPoint CEditElement::IndexToInsertPoint(ElementIndex index, XP_Bool bStickyAfter) { if ( index < 0 ) { XP_ASSERT(FALSE); index = 0; } CEditElement* pChild; CEditElement* pLastChild = NULL; ElementIndex childCount = 0; // XP_TRACE(("IndexToInsertPoint: 0x%08x (%d) index = %d", this, GetElementIndex(), index)); for ( pChild = GetChild(); pChild; pChild = pChild->GetNextSibling()) { pLastChild = pChild; childCount = pChild->GetPersistentCount(); if ( index < childCount || (index == childCount && ! bStickyAfter) ){ return pChild->IndexToInsertPoint(index, bStickyAfter); } index -= childCount; } if ( ! pLastChild ) { // No children at all childCount = GetPersistentCount(); if ( index > childCount ){ XP_ASSERT(FALSE); index = childCount; } return CEditInsertPoint(this, index); } // Ran off end of children return pLastChild->IndexToInsertPoint(childCount, bStickyAfter); } CPersistentEditInsertPoint CEditElement::GetPersistentInsertPoint(ElementOffset offset){ XP_ASSERT(FALSE); // This method should never be called return CPersistentEditInsertPoint(GetElementIndex() + offset); } ElementIndex CEditElement::GetPersistentCount() { ElementIndex count = 0; for ( CEditElement* c = GetChild(); c; c = c->GetNextSibling() ) { count += c->GetPersistentCount(); } return count; } ElementIndex CEditElement::GetElementIndex() { CEditElement* parent = GetParent(); if ( parent ) return parent->GetElementIndexOf(this); else return 0; } ElementIndex CEditElement::GetElementIndexOf(CEditElement* child) { ElementIndex index = GetElementIndex(); for ( CEditElement* c = GetChild(); c; c = c->GetNextSibling() ) { if ( child == c ) { return index; } index += c->GetPersistentCount(); } XP_ASSERT(FALSE); // Couldn't find this child. return index; } void CEditElement::FinishedLoad( CEditBuffer* pBuffer ){ CEditElement* pNext = 0; for ( CEditElement* pChild = GetChild(); pChild; pChild = pNext ) { pNext = pChild->GetNextSibling(); if ( IsAcceptableChild(*pChild) ){ pChild->FinishedLoad(pBuffer); } else { #ifdef DEBUG XP_TRACE(("Removing an unacceptable child. Parent type %d child type %d.\n", GetElementType(), pChild->GetElementType())); #endif delete pChild; } } } // // Containers can't be deleted during adjustment or we will blow up. We need // While adjusting containers, new containers are inserted. // void CEditElement::AdjustContainers( CEditBuffer* pBuffer ){ for ( CEditElement* pChild = GetChild(); pChild; pChild = pChild->GetNextSibling() ) { pChild->AdjustContainers(pBuffer); } } int16 CEditElement::GetWinCSID(){ // International Character set ID CEditRootDocElement* pRoot = GetRootDoc(); if ( pRoot ) { CEditBuffer* pBuffer = pRoot->GetEditBuffer(); if ( pBuffer ) { return pBuffer->GetRAMCharSetID(); } } XP_ASSERT(FALSE); return CS_FE_ASCII; } void CEditElement::EnsureSelectableSiblings(CEditBuffer* pBuffer) { // To ensure that we can edit before or after the table, // make sure that there is a container before and after the table. CEditElement* pParent = GetParent(); if ( ! pParent ) { return; } { // Make sure the previous sibling exists and is a container CEditElement* pPrevious = GetPreviousSibling(); // EXPERIMENTAL: Don't force another container before inserted table if (pPrevious && pPrevious->GetType() == P_DIVISION) pPrevious = pPrevious->GetChild(); #if 0 if ( ! pPrevious ) { #else if ( ! pPrevious || !pPrevious->IsContainer() ) { #endif pPrevious = CEditContainerElement::NewDefaultContainer( NULL, pParent->GetDefaultAlignment() ); pPrevious->InsertBefore(this); pPrevious->FinishedLoad(pBuffer); } } { // Make sure the next sibling exists and is container CEditElement* pNext = GetNextSibling(); if (pNext && pNext->GetType() == P_DIVISION) pNext = pNext->GetChild(); #if 0 if ( ! pNext || pNext->IsEndContainer() ) { #else if ( ! pNext || pNext->IsEndContainer() || !pNext->IsContainer()) { #endif pNext = CEditContainerElement::NewDefaultContainer( NULL, pParent->GetDefaultAlignment() ); pNext->InsertAfter(this); pNext->FinishedLoad(pBuffer); } } } //----------------------------------------------------------------------------- // Reverse navagation (left) //----------------------------------------------------------------------------- // these routines are a little expensive. If we need to, we can make the linked // lists of elements, doubly linked. // CEditElement* CEditElement::GetPreviousSibling(){ if( GetParent() == 0 ){ return 0; } // point to our first sibling. CEditElement *pSibling = GetParent()->GetChild(); // if we are the first sibling, then there is no previous sibling. if ( pSibling == this ){ return 0; } // if we get an Exception in this loop, the tree is messed up! while( pSibling->GetNextSibling() != this ){ pSibling = pSibling->GetNextSibling(); } return pSibling; } void CEditElement::SetChild(CEditElement *pChild){ RawSetChild(pChild); } void CEditElement::SetNextSibling(CEditElement* pNext){ RawSetNextSibling(pNext); } CEditElement* CEditElement::GetLastChild(){ CEditElement* pChild; if( (pChild = GetChild()) == 0 ){ return 0; } while( pChild->GetNextSibling() ){ pChild = pChild->GetNextSibling(); } return pChild; } CEditElement* CEditElement::GetFirstMostChild(){ CEditElement* pChild = this; CEditElement* pPrev = pChild; while( pPrev ){ pChild = pPrev; pPrev = pPrev->GetChild(); } return pChild; } CEditElement* CEditElement::GetLastMostChild(){ CEditElement* pChild = this; CEditElement* pNext = pChild; while( pNext ){ pChild = pNext; pNext = pNext->GetLastChild(); } return pChild; } CEditContainerElement* CEditElement::GetPreviousNonEmptyContainer() { CEditElement *pPrev = GetPreviousSibling(); CEditContainerElement* pPrevContainer; while (pPrev && pPrev->IsContainer()) { pPrevContainer=pPrev->Container(); if (!pPrevContainer->IsEmpty()) return pPrevContainer; pPrev=pPrev->GetPreviousSibling(); } return NULL; } CEditContainerElement* CEditElement::GetNextNonEmptyContainer() { CEditElement *pNext = GetNextSibling(); CEditContainerElement* pNextContainer; while (pNext&& pNext->IsContainer()) { pNextContainer=pNext->Container(); if (!pNextContainer->IsEmpty()) return pNextContainer; pNext=pNext->GetNextSibling(); } return NULL; } /////////////////////////////////////////////// /////END CEDITELEMENT IMPLEMENTATION/////////// /////////////////////////////////////////////// CEditTableCellElement* CEditElement::GetTableCell() { // Returns containing cell, or NULL if none. CEditElement* pElement = this; while ( pElement ) { if ( pElement->IsTableCell() ) { break; } if ( pElement->IsSubDoc() && pElement != this ) { return NULL; } pElement = pElement->GetParent(); } return (CEditTableCellElement*) pElement; } CEditTableCellElement* CEditElement::GetTableCellIgnoreSubdoc() { // Returns containing cell, or NULL if none. CEditElement* pElement = this; while ( pElement ) { if ( pElement->IsTableCell() ) { break; } pElement = pElement->GetParent(); } return (CEditTableCellElement*) pElement; } CEditTableRowElement* CEditElement::GetTableRow() { // Returns containing cell, or NULL if none. CEditElement* pElement = this; while ( pElement ) { if ( pElement->IsTableRow() ) { break; } if ( pElement->IsSubDoc() && pElement != this ) { return NULL; } pElement = pElement->GetParent(); } return (CEditTableRowElement*) pElement; } CEditTableRowElement* CEditElement::GetTableRowIgnoreSubdoc() { // Returns containing cell, or NULL if none. CEditElement* pElement = this; while ( pElement ) { if ( pElement->IsTableRow() ) { break; } pElement = pElement->GetParent(); } return (CEditTableRowElement*) pElement; } CEditCaptionElement* CEditElement::GetCaption() { // Returns containing tavle, or NULL if none. CEditElement* pElement = this; while ( pElement ) { if ( pElement->IsCaption() ) { break; } if ( pElement->IsSubDoc() && pElement != this ) { return NULL; } pElement = pElement->GetParent(); } return (CEditCaptionElement*) pElement; } CEditCaptionElement* CEditElement::GetCaptionIgnoreSubdoc() { // Returns containing table, or NULL if none. CEditElement* pElement = this; while ( pElement ) { if ( pElement->IsCaption() ) { break; } pElement = pElement->GetParent(); } return (CEditCaptionElement*) pElement; } CEditTableElement* CEditElement::GetTable() { // Returns containing table, or NULL if none. CEditElement* pElement = this; while ( pElement ) { if ( pElement->IsTable() ) { break; } if ( pElement->IsSubDoc() && pElement != this ) { return NULL; } pElement = pElement->GetParent(); } return (CEditTableElement*) pElement; } CEditTableElement* CEditElement::GetTableIgnoreSubdoc() { // Returns containing table, or NULL if none. CEditElement* pElement = this; while ( pElement ) { if ( pElement->IsTable() ) { break; } pElement = pElement->GetParent(); } return (CEditTableElement*) pElement; } CEditElement* CEditElement::GetTopmostTableOrLayer() { // Returns containing table, or NULL if none. CEditElement* pResult = NULL; CEditElement* pElement = this; while ( pElement ) { if ( pElement->IsTable() || pElement->IsLayer()) { pResult = pElement; } pElement = pElement->GetParent(); } return pResult; } CEditElement* CEditElement::GetTableOrLayerIgnoreSubdoc() { // Returns containing table, or NULL if none. CEditElement* pElement = this; while ( pElement ) { if ( pElement->IsTable() || pElement->IsLayer() ) { break; } pElement = pElement->GetParent(); } return pElement; } CEditElement* CEditElement::GetSubDocOrLayerSkipRoot() { // Returns containing sub-doc, or NULL if none. CEditElement* pElement = this; while ( pElement ) { if ( pElement->IsLayer() || (pElement->IsSubDoc() && !pElement->IsRoot() ) ) { break; } pElement = pElement->GetParent(); } return pElement; } CEditLayerElement* CEditElement::GetLayer() { // Returns containing Layer, or NULL if none. CEditElement* pElement = this; while ( pElement ) { if ( pElement->IsLayer() ) { break; } if ( pElement->IsSubDoc() && pElement != this ) { return NULL; } pElement = pElement->GetParent(); } return (CEditLayerElement*) pElement; } CEditLayerElement* CEditElement::GetLayerIgnoreSubdoc() { // Returns containing Layer, or NULL if none. CEditElement* pElement = this; while ( pElement ) { if ( pElement->IsLayer() ) { break; } pElement = pElement->GetParent(); } return (CEditLayerElement*) pElement; } CEditSubDocElement* CEditElement::GetSubDoc() { // Returns containing sub-doc, or NULL if none. CEditElement* pElement = this; while ( pElement ) { if ( pElement->IsSubDoc() ) { break; } pElement = pElement->GetParent(); } return (CEditSubDocElement*) pElement; } CEditSubDocElement* CEditElement::GetSubDocSkipRoot() { // Returns containing sub-doc, or NULL if none. CEditElement* pElement = this; while ( pElement ) { if ( pElement->IsSubDoc() && !pElement->IsRoot() ) { break; } pElement = pElement->GetParent(); } return (CEditSubDocElement*) pElement; } CEditRootDocElement* CEditElement::GetRootDoc(){ // Returns containing root. CEditElement* pElement = this; while ( pElement ) { if ( pElement->IsRoot() ) { break; } pElement = pElement->GetParent(); } return (CEditRootDocElement*) pElement; } XP_Bool CEditElement::InMungableMailQuote(){ // Returns true if this element is within a mungable mail quote. CEditElement* pElement = this; while ( pElement ) { if ( pElement->IsSubDoc() ) { return FALSE; } else if ( pElement->IsList() ) { CEditListElement* pList = pElement->List(); if ( pList->IsMailQuote() ) { return TRUE; } } pElement = pElement->GetParent(); } return FALSE; } XP_Bool CEditElement::InMailQuote(){ // Returns true if this element is within a mail quote. return (GetMailQuote() != NULL); } CEditListElement* CEditElement::GetMailQuote() { CEditElement* pElement = this; while ( pElement ) { if ( pElement->IsList() ) { CEditListElement* pList = pElement->List(); if ( pList->IsMailQuote() ) { return pList; } } pElement = pElement->GetParent(); } return NULL; } // This is silly -- we never return anything other than ED_ALIGN_DEFAULT??? ED_Alignment CEditElement::GetDefaultAlignment(){ if ( m_pParent ) return m_pParent->GetDefaultAlignment(); return ED_ALIGN_DEFAULT; } ED_Alignment CEditElement::GetDefaultVAlignment(){ if ( m_pParent ) return m_pParent->GetDefaultVAlignment(); return ED_ALIGN_TOP; } CEditElement* CEditElement::UpLeft( PMF_EditElementTest pmf, void *pTestData ){ CEditElement *pPrev = this; CEditElement* pRet; while( (pPrev = pPrev->GetPreviousSibling()) != NULL ){ pRet = pPrev->DownLeft( pmf, pTestData ); if( pRet ){ return pRet; } } if( GetParent() ){ return GetParent()->UpLeft( pmf, pTestData ); } else{ return 0; } } // // All the children come before the node. // CEditElement* CEditElement::DownLeft( PMF_EditElementTest pmf, void *pTestData, XP_Bool /* bIgnoreThis */ ){ CEditElement *pChild; CEditElement *pRet; pChild = GetLastChild(); while( pChild != NULL ){ if( (pRet = pChild->DownLeft( pmf, pTestData )) != NULL ){ return pRet; } pChild = pChild->GetPreviousSibling(); } if( TestIsTrue( pmf, pTestData ) ){ return this; } return 0; } CEditElement* CEditElement::FindPreviousElement( PMF_EditElementTest pmf, void *pTestData ){ return UpLeft( pmf, pTestData ); } //----------------------------------------------------------------------------- // forward navagation (right) //----------------------------------------------------------------------------- CEditElement* CEditElement::UpRight( PMF_EditElementTest pmf, void *pTestData ){ CEditElement *pNext = this; CEditElement* pRet; while( (pNext = pNext->GetNextSibling()) != NULL ){ pRet = pNext->DownRight( pmf, pTestData ); if( pRet ){ return pRet; } } if( GetParent() ){ return GetParent()->UpRight( pmf, pTestData ); } else{ return 0; } } CEditElement* CEditElement::DownRight( PMF_EditElementTest pmf, void *pTestData, XP_Bool bIgnoreThis ){ CEditElement *pChild; CEditElement *pRet; if( !bIgnoreThis && TestIsTrue( pmf, pTestData ) ){ return this; } pChild = GetChild(); while( pChild != NULL ){ if( (pRet = pChild->DownRight( pmf, pTestData )) != NULL ){ return pRet; } pChild = pChild->GetNextSibling(); } return 0; } CEditElement* CEditElement::FindNextElement( PMF_EditElementTest pmf, void *pTestData ){ CEditElement *pRet; pRet = DownRight( pmf, pTestData, TRUE ); if( pRet ){ return pRet; } return UpRight( pmf, pTestData ); } // // routine looks for a valid text block for positioning during editing. // XP_Bool CEditElement::FindText( void* /*pVoid*/ ){ CEditElement *pPrev; // // if this is a text block and the layout element actually points to // something, return it. // if( GetType() == P_TEXT ){ CEditTextElement *pText = Text(); if( pText->GetLen() == 0 ){ // // Find only empty text blocks that occur at the beginning of // a paragraph (as a paragraph place holder) // pPrev = FindPreviousElement( &CEditElement::FindTextAll, 0 ); if( pPrev && pPrev->FindContainer() == FindContainer() ){ return FALSE; } } return TRUE; } return FALSE; } XP_Bool CEditElement::FindImage( void* /*pVoid*/ ){ return IsImage() ; } XP_Bool CEditElement::FindTarget( void* /*pVoid*/ ){ return GetElementType() == eTargetElement ; } XP_Bool CEditElement::FindUnknownHTML( void* /*pVoid*/ ){ return IsLeaf() && Leaf()->IsUnknownHTML(); } // // routine looks for a valid text block for positioning during editing. // XP_Bool CEditElement::FindLeaf( void* pVoid ){ if( !IsLeaf() ){ return FALSE; } if( IsA(P_TEXT) ){ return FindText( pVoid ); } else { return TRUE; } } XP_Bool CEditElement::FindTextAll( void* /*pVoid*/ ){ // // if this is a text block and the layout element actually points to // something, return it. // if( GetType() == P_TEXT ){ return TRUE; } return FALSE; } XP_Bool CEditElement::FindLeafAll( void* /*pVoid*/ ){ // // if this is a text block and the layout element actually points to // something, return it. // if( IsLeaf() ){ return TRUE; } return FALSE; } XP_Bool CEditElement::FindTable( void* /*pVoid*/ ){ return IsTable(); } XP_Bool CEditElement::FindTableRow( void* /*pVoid*/ ){ return IsTableRow(); } XP_Bool CEditElement::FindTableCell( void* /*pVoid*/ ){ return IsTableCell(); } XP_Bool CEditElement::SplitContainerTest( void* /*pVoid*/ ){ return BitSet( edt_setTextContainer, GetType() ); } XP_Bool CEditElement::SplitFormattingTest( void* pVoid ){ return (void*)GetType() == pVoid; } XP_Bool CEditElement::FindContainer( void* /*pVoid*/ ){ return IsContainer(); } XP_Bool CEditElement::GetWidth(XP_Bool * pPercent, int32 * pWidth) { PA_Tag *pTag = TagOpen(0); if( !pTag ){ return FALSE; } XP_Bool bPercent; int32 iWidth; XP_Bool bDefined = edt_FetchDimension( pTag, PARAM_WIDTH, &iWidth, &bPercent, 0, FALSE, GetWinCSID() ); if( bDefined ){ if( pPercent ){ *pPercent = bPercent; } if( pWidth ){ *pWidth = iWidth; } } return bDefined; } XP_Bool CEditElement::GetHeight(XP_Bool * pPercent, int32 * pHeight) { PA_Tag *pTag = TagOpen(0); if( !pTag ){ return FALSE; } XP_Bool bPercent; int32 iHeight; XP_Bool bDefined = edt_FetchDimension( pTag, PARAM_HEIGHT, &iHeight, &bPercent, 0, FALSE, GetWinCSID() ); if( bDefined ){ if( pPercent ){ *pPercent = bPercent; } if( pHeight ){ *pHeight = iHeight; } } return bDefined; } void CEditElement::SetSize(XP_Bool /* bWidthPercent */, int32 /* iWidth */, XP_Bool /* bHeightPercent */, int32 /* iHeight */){ } XP_Bool CEditElement::CanReflow() { return TRUE; } //----------------------------------------------------------------------------- // Default printing routines. //----------------------------------------------------------------------------- void CEditElement::PrintOpen( CPrintState *pPrintState ){ InternalPrintOpen(pPrintState, m_pTagData); } void CEditElement::InternalPrintOpen( CPrintState *pPrintState, char* pTagData ){ if( !(BitSet( edt_setCharFormat, GetType()) || BitSet( edt_setSuppressNewlineBefore, GetType())) ){ pPrintState->m_pOut->Write( "\n", 1 ); pPrintState->m_iCharPos = 0; } if( pTagData && *pTagData != '>' ){ char *pStr = pTagData; while( *pStr == ' ' ) pStr++; // Trim trailing white-space in-place { intn len = XP_STRLEN(pStr); while ( len > 1 && pStr[len-2] == ' ') { len--; } if ( len > 1 ) { pStr[len-1] = '>'; pStr[len] = '\0'; } } pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "<%s %s", EDT_TagString(GetType()),pStr); } else { pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "<%s>", EDT_TagString(GetType()) ); } if ( BitSet( edt_setRequireNewlineAfter, GetType()) ){ pPrintState->m_pOut->Write( "\n", 1 ); pPrintState->m_iCharPos = 0; } } void CEditElement::PrintEnd( CPrintState *pPrintState ){ if( TagHasClose( GetType() ) || BitSet( edt_setWriteEndTag, GetType() ) ){ pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "", EDT_TagString(GetType()) ); if( !BitSet( edt_setCharFormat, GetType() ) ){ pPrintState->m_pOut->Write( "\n", 1 ); pPrintState->m_iCharPos = 0; } } } //----------------------------------------------------------------------------- // Insertion routines //----------------------------------------------------------------------------- CEditElement* CEditElement::InsertAfter( CEditElement *pPrev ){ XP_ASSERT(m_pParent == NULL); m_pParent = pPrev->GetParent(); SetNextSibling(pPrev->GetNextSibling()); pPrev->SetNextSibling(this); return m_pParent; } CEditElement* CEditElement::InsertBefore( CEditElement *pNext ){ XP_ASSERT(m_pParent == NULL); XP_ASSERT(pNext != NULL); CEditElement *pPrev = pNext->GetPreviousSibling(); if( pPrev == 0 ){ InsertAsFirstChild( pNext->GetParent() ); } else { m_pParent = pNext->GetParent(); SetNextSibling(pPrev->m_pNext); pPrev->SetNextSibling(this); } return m_pParent; } void CEditElement::InsertAsFirstChild( CEditElement *pParent ){ XP_ASSERT(m_pParent == NULL); m_pParent = pParent; SetNextSibling(pParent->GetChild()); pParent->SetChild(this); } void CEditElement::InsertAsLastChild( CEditElement *pParent ){ XP_ASSERT(m_pParent == NULL); m_pParent = pParent; SetNextSibling( 0 ); CEditElement *pPrev = pParent->GetLastChild(); if( pPrev == 0 ){ pParent->SetChild(this); } else { pPrev->SetNextSibling(this); } } CEditElement* CEditElement::Split( CEditElement *pLastChild, CEditElement* pCloneTree, PMF_EditElementTest pmf, void *pData ){ CEditElement *pClone = Clone(); pClone->SetChild(pCloneTree); if( pLastChild->m_pNext ){ if( pCloneTree != 0 ){ pCloneTree->SetNextSibling(pLastChild->m_pNext); } else { pClone->SetChild(pLastChild->m_pNext); } pLastChild->SetNextSibling( 0 ); } // // Reparent all the children that have been moved to the clone. // CEditElement* pNext = pClone->m_pChild; while( pNext ){ pNext->m_pParent = pClone; pNext = pNext->GetNextSibling(); } // // If we are at the container point // if( TestIsTrue( pmf, pData ) ){ return pClone->InsertAfter( this ); } else { return GetParent()->Split( this, pClone, pmf, pData ); } } CEditElement* CEditElement::Clone( CEditElement *pParent ){ return new CEditElement(pParent, GetType(), GetTagData()); } // // Copied formatting, returns the bottom of the chain (Child most formatting // element) // CEditElement* CEditElement::CloneFormatting( TagType endType ){ if( GetType() == endType ){ return 0; } else { return Clone( GetParent()->CloneFormatting(endType) ); } } XP_Bool CEditElement::IsFirstInContainer(){ CEditElement *pPrevious, *pContainer, *pPreviousContainer; pPrevious = PreviousLeaf(); if( pPrevious ){ pContainer = FindContainer(); pPreviousContainer = pPrevious->FindContainer(); if( pContainer != pPreviousContainer ){ return TRUE; } else { return FALSE; } } else { return TRUE; } } CEditTextElement* CEditElement::PreviousTextInContainer(){ CEditElement *pPrevious, *pContainer, *pPreviousContainer; pPrevious = FindPreviousElement( &CEditElement::FindText, 0 ); if( pPrevious ){ pContainer = FindContainer(); pPreviousContainer = pPrevious->FindContainer(); if( pContainer != pPreviousContainer ){ return 0; } else { return pPrevious->Text(); } } else { return 0; } } CEditTextElement* CEditElement::TextInContainerAfter(){ CEditElement *pNext, *pContainer, *pNextContainer; pNext = FindNextElement( &CEditElement::FindText, 0 ); if( pNext ){ pContainer = FindContainer(); pNextContainer = pNext->FindContainer(); if( pContainer != pNextContainer ){ return 0; } else { return pNext->Text(); } } else { return 0; } } CEditLeafElement* CEditElement::PreviousLeafInContainer(){ CEditElement *pPrevious, *pContainer, *pPreviousContainer; pPrevious = PreviousLeaf(); if( pPrevious ){ pContainer = FindContainer(); pPreviousContainer = pPrevious->FindContainer(); if( pContainer != pPreviousContainer ){ return 0; } else { return pPrevious->Leaf(); } } else { return 0; } } CEditLeafElement* CEditElement::LeafInContainerAfter(){ CEditElement *pNext, *pContainer, *pNextContainer; pNext = NextLeaf(); if( pNext ){ pContainer = FindContainer(); pNextContainer = pNext->FindContainer(); if( pContainer != pNextContainer ){ return 0; } else { return pNext->Leaf(); } } else { return 0; } } CEditLeafElement* CEditElement::NextLeafAll(XP_Bool bForward){ if ( bForward ) { return (CEditLeafElement*) FindNextElement(&CEditElement::FindLeafAll, 0 ); } else { return (CEditLeafElement*) FindPreviousElement(&CEditElement::FindLeafAll, 0 ); } } CEditElement* CEditElement::GetRoot(){ CEditElement* pRoot = this; while ( pRoot ) { CEditElement* pParent = pRoot->GetParent(); if ( ! pParent ) break; pRoot = pParent; } return pRoot; } CEditElement* CEditElement::GetCommonAncestor(CEditElement* pOther){ if ( this == pOther ) return this; // Start at root, and trickle down to find where they diverge CEditElement* pRoot = GetRoot(); CEditElement* pRootOther = pOther->GetRoot(); if ( pRoot != pRootOther ) return NULL; CEditElement* pCommon = pRoot; while ( pCommon ){ CEditElement* pAncestor = pCommon->GetChildContaining(this); CEditElement* pAncestorOther = pCommon->GetChildContaining(pOther); if ( pAncestor != pAncestorOther ) break; pCommon = pAncestor; } return pCommon; } CEditElement* CEditElement::GetChildContaining(CEditElement* pDescendant){ CEditElement* pParent = pDescendant; while ( pParent ) { CEditElement* pTemp = pParent->GetParent(); if ( pTemp == this ) break; // Our direct child pParent = pTemp; } return pParent; } XP_Bool CEditElement::IsAcceptableChild(CEditElement& /* pChild */) {return TRUE;} // // Unlink from parent, but keep children // void CEditElement::Unlink(){ CEditElement* pParent = GetParent(); if( pParent ){ CEditElement *pPrev = GetPreviousSibling(); if( pPrev ){ pPrev->SetNextSibling( GetNextSibling() ); } else { pParent->SetChild(GetNextSibling()); } m_pParent = 0; SetNextSibling( 0 ); } } // LTNOTE: 01/01/96 // We take take the characteristics of the thing that follows this paragraph // instead of keeping this paragraphs characteristics. // // jhp -- paste takes characteristic of second, but backspace takes // characteristic of first, so we need a flag to control it. // bCopyAppendAttributes == TRUE for the second (cut/paste) // bCopyAppendAttributes == FALSE for the first (backspace/delete) void CEditElement::Merge( CEditElement *pAppendBlock, XP_Bool bCopyAppendAttributes ){ CEditElement* pChild = GetLastChild(); CEditElement* pAppendChild = pAppendBlock->GetChild(); // LTNOTE: 01/01/96 - I don't think this should be happening anymore. // The way we deal with leaves and containers should keep this from // occuring... // // Check to see if pAppendBlock is a child of this. // // LTNOTE: this is a case where we have // UL: // text: some text // LI: // text: XXX some More text // LI: // text: and some more. // // Merge occurs before xxx // CEditElement *pAppendParent = pAppendBlock->GetParent(); CEditElement *pAppendBlock2 = pAppendBlock; XP_Bool bDone = FALSE; while( !bDone && pAppendParent ){ if( pAppendParent == this ){ pChild = pAppendBlock2->GetPreviousSibling(); bDone = TRUE; } else { pAppendBlock2 = pAppendParent; pAppendParent = pAppendParent->GetParent(); } } pAppendBlock->Unlink(); if( pChild == 0 ){ m_pChild = pAppendChild; } else { CEditElement *pOldNext = pChild->m_pNext; pChild->SetNextSibling( pAppendChild ); // kind of sleazy. we haven't updated the link, GetLastChild will // return the end of pAppendBlock's children. GetLastChild()->SetNextSibling( pOldNext ); } pAppendBlock->m_pChild = 0; while( pAppendChild ){ pAppendChild->m_pParent = this; pAppendChild = pAppendChild->GetNextSibling(); } // // LTNOTE: whack the container type to reflect the thing we just pulled // up. // //XP_ASSERT( IsContainer() && pAppendBlock->IsContainer() ); if( bCopyAppendAttributes && IsContainer() && pAppendBlock->IsContainer() ){ Container()->CopyAttributes( pAppendBlock->Container() ); } delete pAppendBlock; } // By default, yes for any non-atomic tag XP_Bool CEditElement::IsContainerContainer(){ return !BitSet( edt_setNoEndTag, GetType()); } // // Search up the tree for the element that contains us. // - start with us. CEditContainerElement* CEditElement::FindContainer(){ CEditElement *pRet = this; while( pRet ){ if ( pRet->IsSubDoc() && pRet != this ) break; if( pRet->IsContainer() ){ return pRet->Container(); } pRet = pRet->GetParent(); } return 0; } // // Search up the tree for the element that contains us. // - skip us. CEditContainerElement* CEditElement::FindEnclosingContainer(){ CEditElement *pRet = this->GetParent(); if ( pRet ){ return pRet->FindContainer(); } return 0; } void CEditElement::FindList( CEditContainerElement*& pContainer, CEditListElement*& pList ) { pList = 0; XP_Bool bDone = FALSE; pContainer = FindEnclosingContainer(); while( !bDone ){ if( pContainer->GetParent()->IsList() ){ pList = pContainer->GetParent()->List(); bDone = TRUE; } else { CEditElement *pParentContainer = pContainer->FindEnclosingContainer(); if( pParentContainer ){ pContainer = pParentContainer->Container(); } else { bDone = TRUE; } } } } CEditElement* CEditElement::FindContainerContainer(){ CEditElement *pRet = this; while( pRet ){ // Don't need explicit subdoc test because // sub-docs are paragraph containers. if ( pRet->IsContainerContainer() ){ return pRet; } pRet = pRet->GetParent(); } return 0; } #ifdef DEBUG void CEditElement::ValidateTree(){ CEditElement* pChild; CEditElement* pLoopFinder; // Make sure that all of our children point to us. // Makes sure all children are valid. // Make sure we don't have an infinite loop of children. // Use a second pointer that goes // around the loop twice as fast -- if it ever catches up with // pChild, there's a loop. (And yes, since you're wondering, // we did run into infinite loops here...) pChild = GetChild(); pLoopFinder = pChild; while( pChild ){ if( pChild->GetParent() != this ){ XP_ASSERT(FALSE); } XP_ASSERT(IsAcceptableChild(*pChild)); pChild->ValidateTree(); for ( int i = 0; i < 2; i++ ){ if ( pLoopFinder ) { pLoopFinder = pLoopFinder->GetNextSibling(); if (pLoopFinder == pChild) { XP_ASSERT(FALSE); return; } } } pChild = pChild->GetNextSibling(); } } #endif CEditElement* CEditElement::Divide(int /* iOffset */){ return this; } XP_Bool CEditElement::DeleteElement(CEditElement* pTellIfKilled){ CEditElement *pKill = this; CEditElement *pParent; XP_Bool bKilled = FALSE; CEditBuffer *pBuffer = GetEditBuffer(); if( pBuffer ) { // Clear the table/cell selection if element is // the selected cell or table, or is contained in a cell or table // Also clears element saved in buffer if = to "this" pBuffer->CleanupForDeletedElement(pKill); } do { pParent = pKill->GetParent(); pKill->Unlink(); if( pKill == pTellIfKilled ){ bKilled = TRUE; } delete pKill; pKill = pParent; } while( pKill->GetChild() == 0 ); return bKilled; } void CEditElement::DeleteChildren(){ CEditElement* pNext = 0; for ( CEditElement* pChild = GetChild(); pChild; pChild = pNext ) { pNext = pChild->GetNextSibling(); pChild->Unlink(); pChild->DeleteChildren(); delete pChild; } } // class CEditSubDocElement CEditSubDocElement::CEditSubDocElement(CEditElement *pParent, int tagType, char* pData) : CEditElement(pParent, tagType, pData) { } CEditSubDocElement::CEditSubDocElement(CEditElement *pParent, PA_Tag *pTag, int16 csid) : CEditElement(pParent, pTag, csid) { } CEditSubDocElement::CEditSubDocElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer) : CEditElement(pStreamIn, pBuffer) { } void CEditSubDocElement::FinishedLoad( CEditBuffer* pBuffer ){ CEditElement* pChild = GetChild(); if ( ! pChild ) { // Subdocs have to have children. // Put an empty paragraph into any empty subdoc. pChild = CEditContainerElement::NewDefaultContainer( this, IsCaption() ? ED_ALIGN_ABSCENTER : GetDefaultAlignment() ); // Creating it inserts it. (void) new CEditTextElement(pChild, 0); } // Put any leaves into a container. CEditContainerElement* pContainer = NULL; CEditElement* pNext = 0; for ( ; pChild; pChild = pNext) { pNext = pChild->GetNextSibling(); // We might unlink pChild if ( ! IsAcceptableChild(*pChild) ){ if ( pChild->IsLeaf() ){ if ( ! pContainer ){ pContainer = CEditContainerElement::NewDefaultContainer(NULL, GetDefaultAlignment()); pContainer->InsertAfter(pChild); } pChild->Unlink(); pChild->InsertAsLastChild(pContainer); } else { XP_TRACE(("CEditSubDocElement: deleteing child of unknown type %d.\n", pChild->GetElementType())); delete pChild; pChild = NULL; } } else { if ( pContainer ){ pContainer->FinishedLoad(pBuffer); pContainer = NULL; } } if ( pChild ) { pChild->FinishedLoad(pBuffer); } } if ( pContainer ){ pContainer->FinishedLoad(pBuffer); pContainer = NULL; } } XP_Bool CEditSubDocElement::Reduce( CEditBuffer* /* pBuffer */ ){ return FALSE; } #define LAYOUT_ARRAY_SIZE 500 // class CEditTableElement // Default table params for constructors and ::NewData() // Its alot easier to edit with > 1 pixels for these // Easier to select, size, and drag cells, rows, and columns #define ED_DEFAULT_TABLE_BORDER 3 #define ED_DEFAULT_CELL_SPACING 3 // Space between edge of cell and contents // Extra here makes it easier to place caret inside a cell #define ED_DEFAULT_CELL_PADDING 3 CEditTableElement::CEditTableElement(intn columns, intn rows) : CEditElement(0, P_TABLE, 0), m_backgroundColor(), m_pBackgroundImage(0), m_iBackgroundSaveIndex(0), m_align(ED_ALIGN_LEFT), m_malign(ED_ALIGN_DEFAULT), m_iWidth(1), m_iWidthPixels(1), m_iHeight(1), m_iHeightPixels(1), m_bWidthPercent(FALSE), m_bHeightPercent(FALSE), m_iCellSpacing(ED_DEFAULT_CELL_SPACING), m_iCellPadding(ED_DEFAULT_CELL_PADDING), m_iCellBorder(0), m_pFirstCellInColumnOrRow(0), m_pNextCellInColumnOrRow(0), m_iGetRow(0), m_iRowY(0), m_iColX(0), m_bSpan(0), m_iRows(0), m_iColumns(0), m_pCurrentCell(0), m_bSaveWidthPercent(FALSE), m_bSaveHeightPercent(FALSE), m_bSaveWidthDefined(FALSE), m_bSaveHeightDefined(FALSE), m_iColumnLayoutSize(LAYOUT_ARRAY_SIZE), m_iRowLayoutSize(LAYOUT_ARRAY_SIZE) { EDT_TRY { for (intn row = 0; row < rows; row++ ){ CEditTableRowElement* pRow = new CEditTableRowElement(columns); pRow->InsertAsFirstChild(this); } m_pColumnLayoutData = (EDT_CellLayoutData*)XP_ALLOC(LAYOUT_ARRAY_SIZE*sizeof(EDT_CellLayoutData)); m_pRowLayoutData = (EDT_CellLayoutData*)XP_ALLOC(LAYOUT_ARRAY_SIZE*sizeof(EDT_CellLayoutData)); } EDT_CATCH_ALL { Finalize(); EDT_RETHROW; } } CEditTableElement::CEditTableElement(CEditElement *pParent, PA_Tag *pTag, int16 csid, ED_Alignment align) : CEditElement(pParent, P_TABLE), m_backgroundColor(), m_pBackgroundImage(0), m_iBackgroundSaveIndex(0), m_align(align), m_malign(ED_ALIGN_DEFAULT), m_iWidth(1), m_iWidthPixels(1), m_iHeight(1), m_iHeightPixels(1), m_bWidthPercent(FALSE), m_bHeightPercent(FALSE), m_iCellSpacing(ED_DEFAULT_CELL_SPACING), m_iCellPadding(ED_DEFAULT_CELL_PADDING), m_iCellBorder(0), m_pFirstCellInColumnOrRow(0), m_pNextCellInColumnOrRow(0), m_iGetRow(0), m_iRowY(0), m_iColX(0), m_bSpan(0), m_iRows(0), m_iColumns(0), m_pCurrentCell(0), m_bSaveWidthPercent(FALSE), m_bSaveHeightPercent(FALSE), m_bSaveWidthDefined(FALSE), m_bSaveHeightDefined(FALSE), m_iColumnLayoutSize(LAYOUT_ARRAY_SIZE), m_iRowLayoutSize(LAYOUT_ARRAY_SIZE) { switch( m_align ) { case ED_ALIGN_CENTER: /* OK case -- 'center' tag. */ m_align = ED_ALIGN_ABSCENTER; break; case ED_ALIGN_LEFT: case ED_ALIGN_ABSCENTER: case ED_ALIGN_RIGHT: break; default: XP_ASSERT(FALSE); /* Falls through */ case ED_ALIGN_DEFAULT: m_align = ED_ALIGN_LEFT; break; } if( pTag ){ EDT_TableData *pData = ParseParams(pTag, csid); pData->align = m_align; SetData(pData); FreeData(pData); } m_pColumnLayoutData = (EDT_CellLayoutData*)XP_ALLOC(LAYOUT_ARRAY_SIZE*sizeof(EDT_CellLayoutData)); m_pRowLayoutData = (EDT_CellLayoutData*)XP_ALLOC(LAYOUT_ARRAY_SIZE*sizeof(EDT_CellLayoutData)); } CEditTableElement::CEditTableElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer) : CEditElement(pStreamIn, pBuffer), m_backgroundColor(), m_iBackgroundSaveIndex(0), m_pBackgroundImage(0), m_iWidth(1), m_iWidthPixels(1), m_iHeight(1), m_iHeightPixels(1), m_bWidthPercent(FALSE), m_bHeightPercent(FALSE), m_iCellSpacing(ED_DEFAULT_CELL_SPACING), m_iCellPadding(ED_DEFAULT_CELL_PADDING), m_iCellBorder(0), m_pFirstCellInColumnOrRow(0), m_pNextCellInColumnOrRow(0), m_iGetRow(0), m_iRowY(0), m_iColX(0), m_bSpan(0), m_iRows(0), m_iColumns(0), m_pCurrentCell(0), m_bSaveWidthPercent(FALSE), m_bSaveHeightPercent(FALSE), m_bSaveWidthDefined(FALSE), m_bSaveHeightDefined(FALSE), m_iColumnLayoutSize(LAYOUT_ARRAY_SIZE), m_iRowLayoutSize(LAYOUT_ARRAY_SIZE) { m_align = (ED_Alignment) pStreamIn->ReadInt(); m_malign = (ED_Alignment) pStreamIn->ReadInt(); // Get size data from stream -- needed for pasting tables/cells // We must get CSID from buffer since element is not in doc yet //TODO: Should we pass in CSID from paste stream instead? PA_Tag* pTag = CEditElement::TagOpen(0); EDT_TableData *pData = ParseParams( pTag, pBuffer->GetRAMCharSetID() ); if(pData) { m_iWidth = pData->iWidth; m_bWidthPercent = pData->bWidthPercent; if( !m_bWidthPercent ) m_iWidthPixels = m_iWidth; m_iHeight = pData->iHeight; m_bHeightPercent = pData->bHeightPercent; if( !m_bHeightPercent ) m_iHeightPixels = m_iHeight; m_iCellSpacing = pData->iCellSpacing; m_iCellPadding = pData->iCellPadding; if( pData->bBorderWidthDefined && pData->iBorderWidth > 0 ) m_iCellBorder = 1; else m_iCellBorder = 0; EDT_FreeTableData(pData); } PA_FreeTag( pTag ); m_pColumnLayoutData = (EDT_CellLayoutData*)XP_ALLOC(LAYOUT_ARRAY_SIZE*sizeof(EDT_CellLayoutData)); m_pRowLayoutData = (EDT_CellLayoutData*)XP_ALLOC(LAYOUT_ARRAY_SIZE*sizeof(EDT_CellLayoutData)); } CEditTableElement::~CEditTableElement(){ delete m_pBackgroundImage; DeleteLayoutData(); // Be sure to unselect the table if it is selected CEditBuffer *pBuffer = GetEditBuffer(); if( pBuffer && (pBuffer->GetSelectedTable() == this) ) { pBuffer->SelectTable(FALSE, NULL, this); } } PA_Tag* CEditTableElement::TagOpen( int iEditOffset ){ return InternalTagOpen(iEditOffset, FALSE); } PA_Tag* CEditTableElement::InternalTagOpen( int iEditOffset, XP_Bool bForPrinting){ PA_Tag *pRet = 0; PA_Tag* pTag; // create the DIV tag if we need to. if( m_align == ED_ALIGN_ABSCENTER || m_align == ED_ALIGN_RIGHT ){ pTag = XP_NEW( PA_Tag ); XP_BZERO( pTag, sizeof( PA_Tag ) ); if( m_align== ED_ALIGN_RIGHT ){ SetTagData( pTag, "ALIGN=right>"); pTag->type = P_DIVISION; } else { SetTagData( pTag, ">"); pTag->type = P_CENTER; } pRet = pTag; } // create the actual table tag EDT_TableData* pTableData = GetData(); char* pTagData = CreateTagData(pTableData, bForPrinting); if ( pTagData ) { pTag = XP_NEW( PA_Tag ); XP_BZERO( pTag, sizeof( PA_Tag ) ); SetTagData( pTag, pTagData ); XP_FREE(pTagData); } else { pTag = CEditElement::TagOpen( iEditOffset ); } FreeData(pTableData); // link the tags together. if( pRet == 0 ){ pRet = pTag; } else { pRet->next = pTag; } return pRet; } PA_Tag* CEditTableElement::TagEnd( ){ PA_Tag *pRet = CEditElement::TagEnd(); if( m_align == ED_ALIGN_ABSCENTER || m_align == ED_ALIGN_RIGHT ){ PA_Tag* pTag = XP_NEW( PA_Tag ); XP_BZERO( pTag, sizeof( PA_Tag ) ); pTag->is_end = TRUE; if( m_align == ED_ALIGN_RIGHT ){ pTag->type = P_DIVISION; } else { pTag->type = P_CENTER; } if( pRet == 0 ){ pRet = pTag; } else { pRet->next = pTag; } } return pRet; } CEditTableRowElement* CEditTableElement::GetFirstRow() { CEditElement *pChild = GetChild(); if( pChild && pChild->IsTableRow() ) { return pChild->TableRow(); } else if( pChild ) { // Most likely we have a caption XP_ASSERT(pChild->IsCaption()); pChild = pChild->GetNextSibling(); if( pChild && pChild->IsTableRow() ) return pChild->TableRow(); } return NULL; } CEditTableCellElement* CEditTableElement::GetFirstCell() { CEditElement *pChild = GetChild(); if( pChild ) { // Skip over caption if( pChild->IsCaption() ) pChild = pChild->GetNextSibling(); if( pChild && pChild->IsTableRow() ) { pChild = pChild->GetChild(); if( pChild && pChild->IsTableCell() ) { // Initialize these also so we can follow // this call with GetNextCellInRow(), GetNextCellInColumn(), // or GetNextCellInTable() m_pCurrentCell = pChild->TableCell(); m_pFirstCellInColumnOrRow = m_pNextCellInColumnOrRow = m_pCurrentCell; return m_pCurrentCell; } } } return NULL; } CEditTableCellElement* CEditTableElement::GetNextCellInTable(intn *pRowCounter) { if( m_pCurrentCell ) m_pCurrentCell = m_pCurrentCell->GetNextCellInTable(pRowCounter); return m_pCurrentCell; } // The number of rows can simply be counted intn CEditTableElement::CountRows() { m_iRows = 0; for ( CEditElement* pChild = GetChild(); pChild; pChild = pChild->GetNextSibling()) { if ( pChild->IsTableRow() ) { m_iRows++; } } return m_iRows; } // Counting columns is more difficult because ROWSPAN > 1 in a cell // pushes cells in following rows to the right. // Thus the total number of geometric columns may be greater // than a simple count of the number of cells in a row // So we use a strategy similar to how we fill in m_pColumnLayoutData: // we count the number of unique X values for all cells intn CEditTableElement::CountColumns() { TXP_GrowableArray_int32 X_Array; CEditTableCellElement *pCell = GetFirstCell(); while( pCell ) { int32 iCellX = pCell->GetX(); XP_Bool bFound = FALSE; // See if cell's X was already found intn iSize = X_Array.Size(); for( intn i=0; i < iSize; i++ ) { if( iCellX == X_Array[i] ) { bFound = TRUE; break; } } if( !bFound ) X_Array.Add(iCellX); pCell = GetNextCellInTable(); } m_iColumns = X_Array.Size(); return m_iColumns; } CEditTableRowElement* CEditTableElement::GetRow(int32 Y, intn *pRow) { intn iRow = GetRowIndex(Y); if( iRow >= 0 ) { CEditTableCellElement *pCell = GetFirstCellAtRowIndex(iRow); if( pRow ) *pRow = iRow; if( pCell ) return (CEditTableRowElement*)pCell->GetParent(); } return NULL; // Old method -- less efficient. // Needed only if we must use GetRow() before table is layed out, // because thats when the m_pRowLayoutData is built (in CEditBuffer::FixupTableData()) #if 0 for ( CEditElement* pChild = GetChild(); pChild; pChild = pChild->GetNextSibling()) { if ( pChild->IsTableRow() ) { // Scan row for first cell that has given Y as top CEditTableCellElement *pCell = (CEditTableCellElement*)(pChild->GetChild()); while( pCell && pCell-IsTableCell() ) { if( Y == pCell->GetY() ) { if( pRow ) *pRow = iRow; return pChild->TableRow(); } pCell = (CEditTableCellElement*)(pCell->GetNextSibling()); } iRow++; } } return NULL; #endif } CEditTableCellElement* CEditTableElement::GetFirstCellInRow(CEditTableCellElement *pCell, XP_Bool bSpan) { return GetFirstCellInRow(pCell->GetY(), bSpan); } // Get first cell of a "real" (geometric) row, based on location // If bSpan == TRUE , this will also get a cell that spans given Y, // even if top of cell != Y // Note: When bSpan is TRUE, we will find a cell in row above if it spans the // given Y. This is OK for current uses, but it really doesn't return // the "first" cell. (See comment below) // If we don't return the spanned cell as "first", GetNextCellInRow would have // to be much more complicated, as it would have to scan rows above to // detect cells spanning the current row CEditTableCellElement* CEditTableElement::GetFirstCellInRow(int32 Y, XP_Bool bSpan) { CEditTableCellElement *pCell = GetFirstCell(); m_iGetRow = 0; m_iRowY = Y; while( pCell ) { int32 iCellY = pCell->GetY(); // Note that a cell can really be FIRST in a spanned row only if it is // really the first cell in logical row (check for previous sibling) // but if we do that, we won't hit the cell at all later. if( (bSpan && Y >= iCellY && Y <= (iCellY + pCell->GetHeight()) /*&& pCell->GetPreviousSibling() == NULL*/ ) || (!bSpan && ( Y == iCellY)) ) { m_pFirstCellInColumnOrRow = m_pNextCellInColumnOrRow = pCell; return pCell; } pCell = pCell->GetNextCellInTable(&m_iGetRow); } return NULL; } // Get next cell at or spaning the same geometric row as found by GetFirstCellInRow() // or use Y from supplied cell CEditTableCellElement* CEditTableElement::GetNextCellInRow(CEditTableCellElement *pCell) { if( pCell ) { // Initialize using given cell, // so we don't always have to call GetFirst... m_pNextCellInColumnOrRow = pCell; m_iRowY = pCell->GetY(); m_bSpan = FALSE; } pCell = m_pNextCellInColumnOrRow->GetNextCellInTable(&m_iGetRow); while( pCell ) { int32 iCellY = pCell->GetY(); if( (m_bSpan && (m_iRowY >= iCellY) && (m_iRowY <= (iCellY + pCell->GetHeight()))) || (!m_bSpan && ( m_iRowY == iCellY)) ) { m_pNextCellInColumnOrRow = pCell; return pCell; } pCell = pCell->GetNextCellInTable(&m_iGetRow); } return NULL; } CEditTableCellElement* CEditTableElement::GetLastCellInRow(CEditTableCellElement *pCell) { if( pCell ) { // Move until next cell doesn't exist CEditTableCellElement *pNextCell; while( pCell && (pNextCell = GetNextCellInRow(pCell)) != NULL ) { pCell = pNextCell; } } return pCell; } CEditTableCellElement* CEditTableElement::GetPreviousCellInRow(CEditTableCellElement *pCell) { if( pCell ) { // Start with the first cell.. CEditTableCellElement *pFirstCell = GetFirstCellInRow(pCell->GetY()); // We are the first cell - no previous exitst if( pFirstCell == pCell ) return NULL; // Move until we find cell before us CEditTableCellElement *pPrevCell = pFirstCell; CEditTableCellElement *pNextCell; while( pPrevCell && (pNextCell = GetNextCellInRow(pPrevCell)) != pCell ) { pPrevCell = pNextCell; } return pPrevCell; } return NULL; } CEditTableCellElement* CEditTableElement::GetFirstCellAtColumnIndex(intn iIndex) { if( iIndex >= 0 && iIndex < m_iColumns ) return m_pColumnLayoutData[iIndex].pEdCell; return NULL; } CEditTableCellElement* CEditTableElement::GetFirstCellAtRowIndex(intn iIndex) { if( iIndex >= 0 && iIndex < m_iRows ) return m_pRowLayoutData[iIndex].pEdCell; return NULL; } // Get the defining left (for columns) and top (for rows) value from index int32 CEditTableElement::GetColumnX(intn iIndex) { if( iIndex < m_iColumns ) return m_pColumnLayoutData[iIndex].X; return 0; } int32 CEditTableElement::GetRowY(intn iIndex) { if( iIndex < m_iRows ) return m_pRowLayoutData[iIndex].Y; return 0; } // Get grid coordinates of a cell intn CEditTableElement::GetColumnIndex(int32 X) { for( intn i=0; i < m_iColumns; i++ ) { if( m_pColumnLayoutData[i].X == X ) return i; } return -1; } intn CEditTableElement::GetRowIndex(int32 Y) { for( intn i=0; i < m_iRows; i++ ) { if( m_pRowLayoutData[i].Y == Y ) return i; } return -1; } CEditTableCellElement* CEditTableElement::GetFirstCellInColumn(CEditTableCellElement *pCell, XP_Bool bSpan) { return GetFirstCellInColumn(pCell->GetX(), bSpan); } // Get first cell of a "real" (geometric) row, based on location // If bSpan is TRUE, this will also get a cell that spans // given X, even if left edge of cell < X CEditTableCellElement* CEditTableElement::GetFirstCellInColumn(int32 X, XP_Bool bSpan) { CEditTableCellElement *pCell = GetFirstCell(); m_iGetRow = 0; m_iColX = X; m_bSpan = bSpan; while( pCell ) { int32 iCellX = pCell->GetX(); // Note: Using GetWidth() is OK (and more efficient) here since all that matters is // the cell's right edge spans (is > than) left-edge X value if( (!bSpan && X == iCellX) || ( bSpan && X >= iCellX && X <= (iCellX + pCell->GetWidth()) ) ) { m_pFirstCellInColumnOrRow = m_pNextCellInColumnOrRow = pCell; return pCell; } pCell = pCell->GetNextCellInTable(&m_iGetRow); } return NULL; } // Get next cell at or spaning the same geometric row as found by GetFirstCellInColumn() // or use X from supplied cell CEditTableCellElement* CEditTableElement::GetNextCellInColumn(CEditTableCellElement *pCell) { if( pCell ) { // Initialize using given cell, // so we don't always have to call GetFirst... m_pNextCellInColumnOrRow = pCell; m_iColX = pCell->GetX(); m_bSpan = FALSE; } pCell = m_pNextCellInColumnOrRow->GetNextCellInTable(&m_iGetRow); while( pCell ) { int32 iCellX = pCell->GetX(); if( (!m_bSpan && m_iColX == iCellX) || // Must have exact match ( m_bSpan && m_iColX >= iCellX && m_iColX <= (iCellX + pCell->GetWidth()) ) ) //GetWidth is OK here { m_pNextCellInColumnOrRow = pCell; return pCell; } pCell = pCell->GetNextCellInTable(&m_iGetRow); } return NULL; } CEditTableCellElement* CEditTableElement::GetLastCellInColumn(CEditTableCellElement *pCell) { if( pCell ) { // Move until next cell doesn't exist CEditTableCellElement *pNextCell; while( pCell && (pNextCell = GetNextCellInColumn(pCell)) != NULL ) { pCell = pNextCell; } } return pCell; } CEditTableCellElement* CEditTableElement::GetPreviousCellInColumn(CEditTableCellElement *pCell) { if( pCell ) { // Start with the first cell.. CEditTableCellElement *pFirstCell = GetFirstCellInColumn(pCell->GetX()); // We are the first cell - no previous exitst if( pFirstCell == pCell ) return NULL; // Move until we find cell before us CEditTableCellElement *pPrevCell = pFirstCell; CEditTableCellElement *pNextCell; while( pPrevCell && (pNextCell = GetNextCellInColumn(pPrevCell)) != pCell ) { pPrevCell = pNextCell; } return pPrevCell; } return NULL; } CEditTableCellElement* CEditTableElement::GetFirstCellInNextColumn(int32 iCurrentColumnX) { intn i; // Scan data to find current cell's column in our layout data for( i = 0; i < m_iColumns; i++ ) { if( iCurrentColumnX == m_pColumnLayoutData[i].X ) break; } if( i+1 >= m_iColumns ) return NULL; // No cell is after the current column return m_pColumnLayoutData[i+1].pEdCell; } CEditTableCellElement* CEditTableElement::GetFirstCellInNextRow(int32 iCurrentRowY) { intn i; // Scan data to find current cell's row in our layout data for( i = 0; i < m_iRows; i++ ) { if( iCurrentRowY == m_pRowLayoutData[i].Y ) break; } if( i+1 >= m_iRows ) return NULL; // No cell is after the current row return m_pRowLayoutData[i+1].pEdCell; } // Figure out how many columns are included between given X values intn CEditTableElement::GetColumnsSpanned(int32 iStartX, int32 iEndX) { if( iStartX == iEndX ) return 0; intn iColumns = 0; for( intn i = 0; i < m_iColumns; i++ ) { int32 iColX = m_pColumnLayoutData[i].X; if( iStartX <= iColX && iEndX > iColX ) iColumns++; } return iColumns; } // Test if any cell in the first row has COLSPAN > 1 XP_Bool CEditTableElement::FirstRowHasColSpan() { CEditTableCellElement* pCell = GetFirstCell(); while( pCell ) { if( pCell->GetColSpan() > 1 ) return TRUE; pCell = GetNextCellInRow(); } return FALSE; } // Currently (4/24/98) not called // This should be done at least the before the first time a table is layed out // when a file is loaded. It fixes problems that cause major headaches with // rowspan and columnspan // TODO: Should we also pad rows to insure that table is rectangular? (no ragged right edge) void CEditTableElement::FixupColumnsAndRows() { CEditTableCellElement *pFirstCell = GetFirstCell(); if( !pFirstCell ) return; TXP_GrowableArray_int32 X_Array; TXP_GrowableArray_int32 Y_Array; CEditTableCellElement *pCell = pFirstCell; while( pCell ) { int32 iCellX = pCell->GetX(); int32 iCellY = pCell->GetY(); XP_Bool bFoundX = FALSE; // See if cell's X was already found intn iSize = X_Array.Size(); for( intn i=0; i < iSize; i++ ) { if( iCellX == X_Array[i] ) { bFoundX = TRUE; break; } } if( !bFoundX ) X_Array.Add(iCellX); pCell = GetNextCellInTable(); } m_iColumns = X_Array.Size(); intn iColumns = CountColumns(); intn iRows = CountRows(); intn i, iMinSpan, iDecrease; // We will find the maximum number of columns in all rows m_iColumns = 0; for ( i = 0; i < iColumns; i++ ) { CEditTableCellElement *pFirstCellInCol = m_pColumnLayoutData[i].pEdCell; pCell = pFirstCellInCol; iMinSpan = 2; // Scan column to check for minimum COLSPAN while( pCell ) { if( pCell->GetColSpan() < iMinSpan ) iMinSpan = pCell->GetColSpan(); // Any cell with value of 1 means column is OK if( iMinSpan == 1 ) break; pCell = GetNextCellInColumn(pCell); } if( iMinSpan > 1 ) { // We have a bad column // Go back through to fixup COLSPAN values pCell = pFirstCellInCol; iDecrease = iMinSpan - 1; while( pCell ) { pCell->DecreaseColSpan(iDecrease); pCell = GetNextCellInColumn(pCell); } } intn iCellsInRow = m_pColumnLayoutData[i].iCellsInRow; CEditTableRowElement *pRow = (CEditTableRowElement*)pCell->GetParent(); if( pRow ) pRow->SetColumns(iCellsInRow); if( m_iColumns > iCellsInRow) m_iColumns = iCellsInRow; } // Do the same for ROWSPAN for ( i = 0; i < iRows; i++ ) { CEditTableCellElement *pFirstCellInRow = m_pRowLayoutData[i].pEdCell; pCell = pFirstCellInRow; iMinSpan = 2; // Scan Row to check for minimum ROWSPAN while( pCell ) { if( pCell->GetRowSpan() < iMinSpan ) iMinSpan = pCell->GetRowSpan(); if( iMinSpan == 1 ) // Row is OK break; pCell = GetNextCellInRow(pCell); } if( iMinSpan > 1 ) { // We have a bad row // Go back through to fixup ROWSPAN values pCell = pFirstCellInRow; iDecrease = iMinSpan - 1; while( pCell ) { pCell->DecreaseRowSpan(iDecrease); pCell = GetNextCellInRow(pCell); } } } } void CEditTableElement::NormalizeCellsPerRow() { CEditTableCellElement *pFirstCell = GetFirstCell(); CEditBuffer *pBuffer = GetEditBuffer(); if( !pFirstCell || !pBuffer ) return; int32 iRows = CountRows(); int32 iArraySize = iRows * sizeof(int32); int32 *pCellsPerRow = (int32*)XP_ALLOC(iArraySize); if( !pCellsPerRow ) return; XP_MEMSET( pCellsPerRow, 0, iArraySize ); CEditTableCellElement *pCell = pFirstCell; intn iRow = 0; intn iPrevRow = 0; int32 iCellsInRow = 0; while( pCell ) { intn iColSpan = pCell->GetColSpan(); intn iRowSpan = pCell->GetRowSpan(); pCellsPerRow[iRow] += iColSpan; // Fixup subsequent rows because of rowspan if( iRowSpan > 1 ) { for( intn j = 1; j < iRowSpan; j++ ) { // We may overrun our array if table is "bad" // because of a ROWSPAN value that exceeds actual // number of rows. Just skip attempts to access a value too high // since there is no row to use the "ExtraColumns" anyway. if( iRow+j < iRows ) pCellsPerRow[iRow+j] += iColSpan; else // Fixup bad cell???? ;//pCell->SetRowSpan(iRows-1); } } pCell = pCell->GetNextCellInTable(&iRow); } XP_ASSERT((iRow+1) == iRows); // Safety check // Find the maximum cells per row intn iTotalRows = pCellsPerRow[0]; intn i; for( i = 1; i < iRows; i++ ) { if( pCellsPerRow[i] > iTotalRows ) iTotalRows = pCellsPerRow[i]; } pBuffer->SetFillNewCellWithSpace(); CEditTableRowElement *pRow = GetFirstRow(); // Now go through rows and add cells as needed for( i = 0; i < iRows; i++ ) { XP_ASSERT(pRow); if( !pRow ) break; intn iExtraCells = iTotalRows - pCellsPerRow[i]; for( intn j = 0; j < iExtraCells; j++ ) { CEditTableCellElement *pNewCell = new CEditTableCellElement(); if( pNewCell ) { // What could we set this to to make CountColumns() work? //pNewCell->SetX(????); pNewCell->InsertAsLastChild(pRow); pNewCell->FinishedLoad(pBuffer); } } pRow = pRow->GetNextRow(); } pBuffer->ClearFillNewCellWithSpace(); } void CEditTableElement::InsertRows(int32 Y, int32 iNewY, intn number, CEditTableElement* pSourceTable, intn iStartColumn, CEditTableCellElement **ppCellForInsertPoint) { if ( number == 0 ) { return; /* A waste of time, but legal. */ } if ( number < 0 || Y < 0 ) { /* Illegal. */ XP_ASSERT(FALSE); return; } CEditTableRowElement* pCurrentRow = GetRow(Y); if ( ! pCurrentRow ) { XP_ASSERT(FALSE); return; } // We must examine cells with same geometric row, // but in different logical rows // So scan all cells near given Y and increase ROWSPAN // where needed and count actual number of cells // to be inserted in new row(s) intn iColumns = 0; CEditBuffer *pBuffer = GetEditBuffer(); XP_ASSERT(pBuffer); CEditTableCellElement *pCellForInsertPoint = NULL; if( pSourceTable ) { CEditTableRowElement *pSourceRow; if( iStartColumn > 0 ) { // Set flag to autoinsert space into // the blank new cells we will insert here pBuffer->SetFillNewCellWithSpace(); //Simplest way to handle offset insert column // is to add blank cells to the source data pSourceRow = pSourceTable->GetFirstRow(); while(pSourceRow) { for( intn i = 0; i < iStartColumn; i++ ) { CEditTableCellElement *pNewCell = new CEditTableCellElement(); if( pNewCell ) { // Fake the X value so CountColumns will work pNewCell->SetX(iStartColumn - i); pNewCell->InsertAsFirstChild(pSourceRow); pNewCell->FinishedLoad(pBuffer); } } pSourceRow = pSourceRow->GetNextRow(); } // Clear this so we don't touch empty cells // from the paste source pBuffer->ClearFillNewCellWithSpace(); } } CEditTableCellElement *pCell = GetFirstCellInRow(Y, TRUE ); while( pCell ) { int32 iCellY = pCell->GetY(); int32 iColSpan = pCell->GetColSpan(); int32 iRowSpan = pCell->GetRowSpan(); if( Y == iNewY ) { // Inserting above if( iCellY == Y ) { // We will be inserting cells above this one iColumns += iColSpan; } else { // Cell is actually in row above, // so just increase its rowspan pCell->IncreaseRowSpan(number); } } else { // Inserting below current row if( iNewY == (iCellY + pCell->GetHeight()) ) { // Bottom of this cell is where we want to insert iColumns += iColSpan; } else { // Cell spans the new insert Y, so just expand some more pCell->IncreaseRowSpan(number); } } pCell = GetNextCellInRow(); } // Now insert the new rows (including iColumns new cells in each) for ( intn row = 0; row < number; row++ ) { CEditTableRowElement* pNewRow; if ( pSourceTable ) { pNewRow = pSourceTable->GetFirstRow(); pNewRow->Unlink(); } else { pNewRow = new CEditTableRowElement(iColumns); if( !pNewRow ) break; } // Set insert point to first cell in inserted rows if( !pCellForInsertPoint ) pCellForInsertPoint = pNewRow->GetFirstCell(); if( Y == iNewY ) pNewRow->InsertBefore(pCurrentRow); else { pNewRow->InsertAfter(pCurrentRow); pCurrentRow = pNewRow; } pNewRow->FinishedLoad(pBuffer); } // Pad the inserted rows or the table with extra cells // to keep rectangular geometry NormalizeCellsPerRow(); if( ppCellForInsertPoint && *ppCellForInsertPoint == NULL ) *ppCellForInsertPoint = pCellForInsertPoint; } // Now returns cell where we should move the insert point void CEditTableElement::InsertColumns(int32 X, int32 iNewX, intn number, CEditTableElement* pSourceTable, intn iStartRow, CEditTableCellElement **ppCellForInsertPoint) { if ( number == 0 ) { return; /* A waste of time, but legal. */ } if ( number < 0 || X < 0 ) { /* Illegal. */ XP_ASSERT(FALSE); return; } CEditBuffer *pBuffer = GetEditBuffer(); XP_ASSERT(pBuffer); CEditTableCellElement *pCellForInsertPoint = NULL; if( pSourceTable ) pCellForInsertPoint = pSourceTable->GetFirstCell(); // Insert starts below top of table - pad source with blank // rows if( pSourceTable && iStartRow > 0 ) { XP_ASSERT(pSourceTable->IsTable()); // Set flag to autoinsert space into new cells according to pref pBuffer->SetFillNewCellWithSpace(); for( intn i = 0; i < iStartRow; i++ ) { // Creating a row with "number" of cells CEditTableRowElement* pRow = new CEditTableRowElement(number); if( pRow ) { pRow->InsertAsFirstChild(pSourceTable); // Insert default space if pref is set pRow->FinishedLoad(pBuffer); } } pBuffer->ClearFillNewCellWithSpace(); } CEditTableRowElement* pRow = GetFirstRow(); CEditTableRowElement* pLastRow = pRow; CEditTableRowElement* pSourceRow = NULL; while( pRow ) { if( pSourceTable ) pSourceRow = pSourceTable->GetFirstRow(); pRow->InsertCells(X, iNewX, number, pSourceRow, &pCellForInsertPoint); if( pSourceRow ) delete pSourceRow; pRow = pRow->GetNextRow(); if( pRow ) pLastRow = pRow; } if( pSourceTable ) { if( !pLastRow ) // Not Likely! goto INSERT_COLUMNS_END; // Do we have any more rows to paste? pSourceRow = pSourceTable->GetFirstRow(); if( pSourceRow ) { // The number of columns we will have to insert if we // need to add rows at bottom of table // so overflow from inserted columns line up intn iColumnsBefore = GetColumnsSpanned(GetColumnX(0), iNewX); while(pSourceRow) { CEditTableRowElement *pNextRow; if( iColumnsBefore ) { // We need cells before the insert column, // so make a new row with blank cells pNextRow = new CEditTableRowElement(iColumnsBefore); if( pNextRow ) { pNextRow->InsertAfter(pLastRow); // Append the source cells to the the new row pNextRow->AppendRow(pSourceRow); } } else { // No extra columns needed, // just append source row pSourceRow->Unlink(); pSourceRow->InsertAfter(pLastRow); pNextRow = pSourceRow; } XP_ASSERT(pNextRow); if( pNextRow ) { // Fill in rest of row with empty cells // DOESN'T WORK WITH ROW NOT ALREADY LAYED OUT //pNextRow->PadRowWithEmptyCells(iTotalColumns); // Be sure any empty cells have required empty text elements pNextRow->FinishedLoad(pBuffer); } pLastRow = pNextRow; pSourceRow = pSourceTable->GetFirstRow(); } } } NormalizeCellsPerRow(); INSERT_COLUMNS_END: if( ppCellForInsertPoint && *ppCellForInsertPoint == NULL ) *ppCellForInsertPoint = pCellForInsertPoint; } XP_Bool CEditTableElement::ReplaceSpecialCells(CEditTableElement *pSourceTable, XP_Bool bIgnoreSourceLayout, CEditTableCellElement **ppCellForInsertPoint ) { CEditTableCellElement *pCellForInsertPoint = NULL; CEditTableCellElement *pReplaceCell; CEditTableCellElement *pSourceCell; CEditTableCellElement *pNextReplaceCell; CEditTableCellElement *pNextSourceCell; intn iReplaceRow = 0; intn iPrevReplaceRow = 0; intn iSourceRow = 0; intn iPrevSourceRow = 0; EDT_TableCellData *pReplaceData; EDT_TableCellData *pSourceData; XP_Bool bAllSourceCellsPasted = TRUE; if( !pSourceTable ) goto REPLACE_DONE; pReplaceCell = GetFirstCell(); if( pReplaceCell ) { // Skip to a special-selected cell while( pReplaceCell && !pReplaceCell->IsSpecialSelected() ) pReplaceCell = pReplaceCell->GetNextCellInTable(&iReplaceRow); } pSourceCell = pSourceTable->GetFirstCell(); if( !pReplaceCell || !pSourceCell ) goto REPLACE_DONE; iPrevReplaceRow = iReplaceRow; do { pReplaceData = pReplaceCell->GetData(); // Very, very unlikely to fail if( !pReplaceData ) goto REPLACE_DONE; // Note that we must get the csid of the current table // because source cell is not part of doc yet pSourceData = pSourceCell->GetData(GetWinCSID()); if( !pSourceData ) { EDT_FreeTableCellData(pReplaceData); goto REPLACE_DONE; } if( iReplaceRow != iPrevReplaceRow ) { // We are on the next row to be replaced iPrevReplaceRow = iReplaceRow; // If cell layout of source and destination matches, // we should get to the next source row at the same time, // (which means iSourceRow should be 1 > than iPrevSourceRow) // if iSourceRow wasn't increased, there's still source cells on the previous row if( iSourceRow == iPrevSourceRow ) { XP_TRACE(("CEditTableElement::ReplaceSpecialCells: More Source cells are left to paste.")); // We may flow cells into source, ignoring the source geometry if( !bIgnoreSourceLayout ) { // Skip over cells to get to the next source row bAllSourceCellsPasted = FALSE; while( pSourceCell && iSourceRow == iPrevSourceRow ) pSourceCell = pSourceCell->GetNextCellInTable(&iSourceRow); if( !pSourceCell ) goto REPLACE_DONE; } iPrevSourceRow = iSourceRow; } } else if( iSourceRow != iPrevSourceRow ) { // We ran out of source cells on the desired row, // but we still have cells to replace XP_TRACE(("CEditTableElement::ReplaceSpecialCells: Not enough SOURCE cells to paste in this row.")); if( !bIgnoreSourceLayout ) { // Skip to the next replace row (skip replacing rest on this row) while( pReplaceCell && iReplaceRow == iPrevReplaceRow ) { // Delete contents in the extra replace cells if( pReplaceCell->IsSpecialSelected() ) pReplaceCell->DeleteContents(); pReplaceCell = pReplaceCell->GetNextCellInTable(&iReplaceRow); } // Skip to the next special selected cell while( pReplaceCell && !pReplaceCell->IsSpecialSelected() ) pReplaceCell = pReplaceCell->GetNextCellInTable(&iReplaceRow); if( !pReplaceCell ) break; // We are now at the right place in the next replace row iPrevReplaceRow = iReplaceRow; } } // Be sure to do this here (not in above block) // since we don't ever go there if cell layout // of source and destination matches iPrevSourceRow = iSourceRow; // Must get these now before we move cells around pNextSourceCell = pSourceCell->GetNextCellInTable(&iSourceRow); pNextReplaceCell = pReplaceCell->GetNextCellInTable(&iReplaceRow); // Skip to a special-selected cell while( pNextReplaceCell && !pNextReplaceCell->IsSpecialSelected() ) pNextReplaceCell = pNextReplaceCell->GetNextCellInTable(&iReplaceRow); // Move source cell to just after cell to be replaced pSourceCell->Unlink(); pSourceCell->InsertAfter(pReplaceCell); // then delete the replace cell delete pReplaceCell; // Use size data from the cell we replaced // to minimize table layout distortion // (pReplaceData is the "source" data) pReplaceData->mask = CF_WIDTH | CF_HEIGHT | CF_COLSPAN | CF_ROWSPAN; edt_CopyTableCellData(pSourceData, pReplaceData); pSourceCell->SetData(pSourceData); EDT_FreeTableCellData(pSourceData); EDT_FreeTableCellData(pReplaceData); // We will place caret in the first cell replaced if( !pCellForInsertPoint ) pCellForInsertPoint = pSourceCell; pReplaceCell = pNextReplaceCell; pSourceCell = pNextSourceCell; } while( pReplaceCell && pSourceCell ); // Delete contents in the remaining replace cells while( pReplaceCell ) { if( pReplaceCell->IsSpecialSelected() ) pReplaceCell->DeleteContents(); pReplaceCell = pReplaceCell->GetNextCellInTable(&iReplaceRow); } // Check if we still have cells in last source row, if( bAllSourceCellsPasted && pSourceCell ) bAllSourceCellsPasted = FALSE; REPLACE_DONE: if( ppCellForInsertPoint && *ppCellForInsertPoint == NULL ) *ppCellForInsertPoint = pCellForInsertPoint; // Note: Caller deletes what remains of the source table GetEditBuffer()->ClearSpecialCellSelection(); return bAllSourceCellsPasted; } // Code moved from CDeleteTableCommand::CDeleteTableCommand, // Note: This version breaks the older, now unused Undo system void CEditTableElement::Delete(XP_Bool bMoveInsertPoint) { CEditBuffer *pBuffer = GetEditBuffer(); if( !pBuffer ) return; CEditElement* pRefreshStart = GetFirstMostChild()->PreviousLeaf(); CEditInsertPoint replacePoint(GetLastMostChild()->NextLeaf(), 0); // Move only if requested to // (This is used when pasting a table, // which inserts and moves insert point BEFORE deleting table) if( bMoveInsertPoint ) pBuffer->SetInsertPoint(replacePoint); // Be sure to unselect the table if it is selected // else Relayout blows up if( pBuffer->GetSelectedTable() == this ) pBuffer->SelectTable(FALSE); Unlink(); // Left over from older Undo system //m_replacePoint = pBuffer()->EphemeralToPersistent(replacePoint); pBuffer->Relayout(pRefreshStart, 0, replacePoint.m_pElement); // Delete ourselves delete this; } // Note: no saving to pUndoContainer any more void CEditTableElement::DeleteRows(int32 Y, intn number, CEditTableCellElement **ppCellForInsertPoint) { if ( number == 0 ) { return; /* A waste of time, but legal. */ } if ( number < 0 || Y < 0 ){ /* Illegal. */ XP_ASSERT(FALSE); return; } intn iRowIndex = GetRowIndex(Y); // Abort if invalid Y value if( iRowIndex == -1 ) return; CEditTableCellElement *pCellForInsertPoint = NULL; // Restrict number of rows we can delete intn iLastIndex = min(iRowIndex + number - 1, m_iRows-1); CEditTableRowElement *pRowAbove = NULL; CEditTableRowElement *pRowBelow = NULL; if( iRowIndex > 0 ) pRowAbove = GetRow(GetRowY(iRowIndex-1)); for( ; iRowIndex <= iLastIndex; iRowIndex++ ) { Y = GetRowY(iRowIndex); CEditTableRowElement *pRow = GetRow(Y); // Get first cell starting at or spanning the Y value (TRUE) CEditTableCellElement *pCell = GetFirstCellInRow(Y, TRUE); while( pCell ) { // Get next cell before we delete the one we're at CEditTableCellElement *pNextCell = GetNextCellInRow(); if( Y == pCell->GetY() ) { // Cell is in same row as delete row // Unselect if selected and delete the cell if( pCell->IsSelected() ) GetEditBuffer()->SelectCell(FALSE, NULL, pCell); delete pCell; // Note: We don't worry about deleting cells with rowspan > 1 // even though that might upset the column layout for cells // below the deleted rows. // We might want to revisit this later -- we would have to // insert blank cells into rows below when rowspan > number // of rows we are deleting } else if( pCell->GetRowSpan() > 1 ) { // Cell spans the delete row // (its actually in a row above the delete row) // Reduce its rowspan instead of deleting pCell->DecreaseRowSpan(1); } #ifdef DEBUG else XP_TRACE(("CEditTableElement::DeleteRows: Rowspan doesnt agree with rowlayout")); #endif pCell = pNextCell; } // Delete the row if all cells were deleted (should always be true?) // VERY IMPORTANT. Never call anything to cause // relayout else our m_pRowLayoutData and entire coordinate // system will be altered if( pRow && pRow->GetChild() == NULL ) { // Get the next row if available before deleting this one pRowBelow = pRow->GetNextRow(); #ifdef DEBUG if( pRowBelow ) XP_ASSERT(pRowBelow->IsTableRow()); #endif delete pRow; } } // Get first cell in the next row after deleted rows, // or row before if we deleted to the end of the table if( pRowBelow ) pCellForInsertPoint = pRowBelow->GetFirstCell(); else if( pRowAbove ) pCellForInsertPoint = pRowAbove->GetFirstCell(); if( ppCellForInsertPoint && *ppCellForInsertPoint == NULL ) *ppCellForInsertPoint = pCellForInsertPoint; } void CEditTableElement::DeleteColumns(int32 X, intn number, CEditTableCellElement **ppCellForInsertPoint) { if ( number == 0 ) { return; /* A waste of time, but legal. */ } if ( number < 0 || X < 0 ) { /* Illegal. */ XP_ASSERT(FALSE); return; } CEditTableCellElement *pCellForInsertPoint = NULL; CEditTableRowElement* pRow = GetFirstRow(); while( pRow ) { // Delete cells from the row and save the first cell found for an insert point pRow->DeleteCells(X, number, &pCellForInsertPoint); CEditTableRowElement *pNextRow = pRow->GetNextRow(); // Check if ALL cells were deleted - delete row if all were if( !pRow->GetChild() ) delete pRow; pRow = pNextRow; } if( ppCellForInsertPoint && *ppCellForInsertPoint == NULL ) *ppCellForInsertPoint = pCellForInsertPoint; } // Returns TRUE if any row was deleted // Return the last cell in row above the first deleted row // if first row wasn't deleted XP_Bool CEditTableElement::DeleteEmptyRows(CEditLeafElement **ppPreviousLeaf) { int32 iRowsInTable = m_iRows; CEditTableRowElement *pRow = GetFirstRow(); CEditTableRowElement *pPrevRow = 0; int32 iCount = 0; int32 iIndex = 0; int32 iFirstEmptyRowY = -1; CEditTableCellElement *pCellForInsertPoint = NULL; // Clear this now in case we don't find a valid leaf to use if( ppPreviousLeaf ) *ppPreviousLeaf = 0; // Count number of completely empty rows while( pRow ) { if( pRow->AllCellsInRowAreEmpty() ) { if( iFirstEmptyRowY == -1 ) iFirstEmptyRowY = GetRowY(iIndex); iCount++; if( iCount == 1 && pPrevRow && ppPreviousLeaf ) { // We are in the first row we will delete and it isn't the first row of table // Get the last cell in the previous row CEditTableCellElement *pCell = pPrevRow->GetFirstCell(); CEditTableCellElement *pNextCell; while( pCell && (pNextCell = GetNextCellInRow(pCell)) != 0 ) pCell = pNextCell; // Return last leaf in that cell if( pCell ) { CEditElement *pLast = pCell->GetLastMostChild(); while( pLast && (!pLast->IsLeaf() || pLast->Leaf()->GetLen() == 0) ) pLast = pLast->GetPreviousSibling(); if( pLast && pLast->IsLeaf() ) *ppPreviousLeaf = pLast->Leaf(); } } } else { pPrevRow = pRow; } iIndex++; pRow = pRow->GetNextRow(); } if( iCount > 0 ) { DeleteRows(iFirstEmptyRowY, iCount); return TRUE; } return FALSE; } // This is the old version - just finds row based on counting, // no ROWSPAN effect needed CEditTableRowElement* CEditTableElement::FindRow(intn number) { intn count = 0; if ( number < 0 ) { XP_ASSERT(FALSE); return NULL; } for ( CEditElement* pChild = GetChild(); pChild; pChild = pChild->GetNextSibling()) { if ( pChild->IsTableRow() ) { if ( count++ == number ) { return (CEditTableRowElement*) pChild; } } } return NULL; } CEditCaptionElement* CEditTableElement::GetCaption(){ // Normally first or last, but we check everybody to be robust. for ( CEditElement* pChild = GetChild(); pChild; pChild = pChild->GetNextSibling()) { if ( pChild->IsCaption() ) { return (CEditCaptionElement*) pChild; } } return NULL; } void CEditTableElement::SetCaption(CEditCaptionElement* pCaption){ DeleteCaption(); if ( pCaption ){ EDT_TableCaptionData* pData = pCaption->GetData(GetWinCSID()); XP_Bool bAddAtTop = ! ( pData->align == ED_ALIGN_BOTTOM || pData->align == ED_ALIGN_ABSBOTTOM); CEditCaptionElement::FreeData(pData); if ( bAddAtTop ){ pCaption->InsertAsFirstChild(this); } else { pCaption->InsertAsLastChild(this); } } } void CEditTableElement::DeleteCaption(){ CEditCaptionElement* pOldCaption = GetCaption(); delete pOldCaption; } void CEditTableElement::DeleteCaptionAbove() { CEditCaptionElement* pCaption = GetCaption(); // A caption "above" the table is always the first // child of the table if( pCaption && pCaption == GetChild() ) delete pCaption; } void CEditTableElement::DeleteCaptionBelow() { CEditCaptionElement* pCaption = GetCaption(); // A caption "below" the table is always the // last of the table. Its simpler to just check // if its not the first child if( pCaption && pCaption != GetChild() ) delete pCaption; } void CEditTableElement::FinishedLoad( CEditBuffer* pBuffer ){ // From experimentation, we know that the 2.0 navigator dumps // tags that aren't in td, tr, or caption cells into the doc // before the table. // // That's a little too radical for me. So what I do is // wrap tds in trs, put anything else // into a caption CEditCaptionElement* pCaption = NULL; CEditTableRowElement* pRow = NULL; CEditElement* pNext = 0; // TODO: MAKE THIS WORK BEFORE LAYOUT HAPPENS (no m_pColumnLayoutData yet) //FixupColumnsAndRows(); // For efficiency, count rows only // if we haven't layed out table if( m_iRows <= 0 ) m_iRows = CountRows(); if ( m_iRows <= 0 ) { // Force a row CEditTableRowElement* pTempRow = new CEditTableRowElement(); pTempRow->InsertAsFirstChild(this); } CEditElement* pChild; for ( pChild = GetChild(); pChild; pChild = pNext) { pNext = pChild->GetNextSibling(); // We might unlink pChild if ( ! IsAcceptableChild(*pChild) ){ if ( pChild->IsTableCell() ){ if ( ! pRow ){ pRow = new CEditTableRowElement(); pRow->InsertAfter(pChild); } pChild->Unlink(); pChild->InsertAsLastChild(pRow); if ( pCaption ){ pCaption->FinishedLoad(pBuffer); pCaption = NULL; } } else { // Put random children into a caption if ( ! pCaption ){ pCaption = new CEditCaptionElement(); pCaption->InsertAfter(pChild); } pChild->Unlink(); pChild->InsertAsLastChild(pCaption); if ( pRow ){ pRow->FinishedLoad(pBuffer); pRow = NULL; } } } else { if ( pRow ){ pRow->FinishedLoad(pBuffer); pRow = NULL; } if ( pCaption ){ pCaption->FinishedLoad(pBuffer); pCaption = NULL; } } pChild->FinishedLoad(pBuffer); } if ( pRow ){ pRow->FinishedLoad(pBuffer); pRow = NULL; } if ( pCaption ){ pCaption->FinishedLoad(pBuffer); pCaption = NULL; } // Merge all captions together. pCaption = NULL; for ( pChild = GetChild(); pChild; pChild = pNext) { pNext = pChild->GetNextSibling(); // We might unlink pChild if ( pChild->IsCaption() ){ if ( pCaption ) { // This is an extra caption. Merge its contents into the other caption. pChild->Unlink(); CEditElement* pItem; while ( ( pItem = pChild->GetChild() ) != NULL ){ pItem->Unlink(); pItem->InsertAsLastChild(pCaption); } delete pChild; } else { pCaption = (CEditCaptionElement*) pChild; } } } AdjustCaption(); EnsureSelectableSiblings(pBuffer); } XP_Bool CEditTableElement::MoveCaptionOutsideTable(XP_Bool bAfter) { CEditCaptionElement *pCaption = GetCaption(); XP_Bool bReturn = FALSE; if ( pCaption ) { CEditContainerElement *pContainer = (CEditContainerElement*)pCaption->GetChild(); // Skip over the division element used for alignment // (Alternate method is to start at first leaf and find its container, // but that would miss lists, so lets try to start from top down) if( pContainer && pContainer->IsDivision() ) pContainer = (CEditContainerElement*)pContainer->GetChild(); if( !pContainer || !pContainer->IsContainer() || (pContainer->IsEmpty() && pContainer->GetNextSibling() == 0) ) return FALSE; CEditElement *pLastChild = GetLastMostChild(); CEditLeafElement *pBefore = bAfter ? 0 : PreviousLeaf(); CEditLeafElement *pAfter = (bAfter && pLastChild) ? pLastChild->NextLeaf() : 0; CEditContainerElement *pOutsideContainer; // Save container after current so all are moved CEditElement *pNextContainer = pContainer->GetNextSibling(); if( pBefore ) { pOutsideContainer = pBefore->FindContainer(); if( pOutsideContainer ) { // Move the first container found pContainer->Unlink(); pContainer->InsertAfter(pOutsideContainer); bReturn = TRUE; } } else if( pAfter ) { pOutsideContainer = pAfter->FindContainer(); if( pOutsideContainer ) { pContainer->Unlink(); pContainer->InsertBefore(pOutsideContainer); bReturn = TRUE; } } if( bReturn && pNextContainer != 0 ) { // Move any other containers following the one just moved CEditElement *pInsertAfter = pContainer; while( pNextContainer ) { pContainer = (CEditContainerElement*)pNextContainer; pNextContainer = pContainer->GetNextSibling(); pContainer->Unlink(); pContainer->InsertAfter(pInsertAfter); pInsertAfter = pContainer; } } } return bReturn; } void CEditTableElement::AdjustCaption() { CEditCaptionElement* pCaption = GetCaption(); if ( pCaption ) { // Now move the caption to the start or the end of the table, as appropriate. pCaption->Unlink(); SetCaption(pCaption); } } void CEditTableElement::StreamOut( IStreamOut *pOut){ CEditElement::StreamOut( pOut ); pOut->WriteInt( (int32)m_align ); pOut->WriteInt( (int32)m_malign ); } void CEditTableElement::SetData( EDT_TableData *pData ){ char *pNew = CreateTagData(pData, FALSE); CEditElement::SetTagData( pNew ); m_align = pData->align; m_malign = pData->malign; // Keep track of alignment seperately to avoid crashing when editing. if ( pNew ) { free(pNew); // XP_FREE? } m_iWidthPixels = pData->iWidthPixels; m_iWidth = pData->iWidth; m_bWidthPercent = pData->bWidthPercent; m_iHeightPixels = pData->iHeightPixels; m_iHeight = pData->iHeight; m_bHeightPercent = pData->bHeightPercent; m_iCellSpacing = pData->iCellSpacing; m_iCellPadding = pData->iCellPadding; if( pData->bBorderWidthDefined && pData->iBorderWidth > 0 ) m_iCellBorder = 1; else m_iCellBorder = 0; m_iRows = pData->iRows; m_iColumns = pData->iColumns; } char* CEditTableElement::CreateTagData(EDT_TableData *pData, XP_Bool bPrinting) { char *pNew = 0; m_align = pData->align; m_malign = pData->malign; if( bPrinting && pData->malign != ED_ALIGN_DEFAULT ){ pNew = PR_sprintf_append( pNew, "ALIGN=%s ", EDT_AlignString(pData->malign) ); } // If you don't mention the border, you get a zero pixel border. // If you just say BORDER, it's like BORDER=1 if ( pData->bBorderWidthDefined ){ if ( pData->iBorderWidth == 1 ) { /* Neatness counts. */ pNew = PR_sprintf_append( pNew, "BORDER "); } else { pNew = PR_sprintf_append( pNew, "BORDER=%d ", pData->iBorderWidth ); } } if ( pData->iCellSpacing != 1 ){ pNew = PR_sprintf_append( pNew, "CELLSPACING=%d ", pData->iCellSpacing ); } if ( pData->iCellPadding != 1 ){ pNew = PR_sprintf_append( pNew, "CELLPADDING=%d ", pData->iCellPadding ); } if ( pData->bUseCols ){ int cols = (int) GetColumns(); //Should we use CountColumns()? if ( cols == 0 ) { cols = (int) pData->iColumns; } pNew = PR_sprintf_append( pNew, "COLS=%d ", cols ); } if( pData->bWidthDefined ){ if( pData->bWidthPercent ){ pNew = PR_sprintf_append( pNew, "WIDTH=\"%ld%%\" ", (long)min(100,max(1,pData->iWidth)) ); } else { pNew = PR_sprintf_append( pNew, "WIDTH=\"%ld\" ", (long)pData->iWidth ); } } if( pData->bHeightDefined ){ if( pData->bHeightPercent ){ pNew = PR_sprintf_append( pNew, "HEIGHT=\"%ld%%\" ", (long)min(100,max(1, pData->iHeight)) ); } else { pNew = PR_sprintf_append( pNew, "HEIGHT=\"%ld\" ", (long)pData->iHeight ); } } if ( pData->pColorBackground ) { SetBackgroundColor(EDT_LO_COLOR(pData->pColorBackground)); pNew = PR_sprintf_append( pNew, "BGCOLOR=\"#%06lX\" ", GetBackgroundColor().GetAsLong() ); } else { SetBackgroundColor(ED_Color::GetUndefined()); } if ( pData->pBackgroundImage ) { SetBackgroundImage(pData->pBackgroundImage); pNew = PR_sprintf_append( pNew, "BACKGROUND=\"%s\" ", pData->pBackgroundImage ); } else { SetBackgroundImage(0); } if ( pData->bBackgroundNoSave) { pNew = PR_sprintf_append( pNew, "NOSAVE " ); } if( pData->pExtra ){ pNew = PR_sprintf_append( pNew, "%s ", pData->pExtra ); } if( pNew ){ pNew = PR_sprintf_append( pNew, ">" ); } return pNew; } EDT_TableData* CEditTableElement::GetData() { EDT_TableData *pRet; PA_Tag* pTag = CEditElement::TagOpen(0); pRet = ParseParams( pTag, GetWinCSID() ); // Alignment isn't kept in the tag data, because tag data is used for editing, // and we crash if we try to edit an aligned table, because layout doesn't // set up the data structures that we need. pRet->align = m_align; pRet->malign = m_malign; // Return the widths determined by layout pRet->iWidth = m_iWidth; pRet->iWidthPixels = m_iWidthPixels; pRet->iHeight = m_iHeight; pRet->iHeightPixels = m_iHeightPixels; pRet->iCellSpacing = m_iCellSpacing; pRet->iCellPadding = m_iCellPadding; pRet->iRows = m_iRows; pRet->iColumns = m_iColumns; PA_FreeTag( pTag ); return pRet; } static char *tableParams[] = { PARAM_ALIGN, PARAM_BORDER, PARAM_CELLSPACE, PARAM_CELLPAD, PARAM_COLS, PARAM_WIDTH, PARAM_HEIGHT, PARAM_BGCOLOR, PARAM_BACKGROUND, PARAM_NOSAVE, 0 }; EDT_TableData* CEditTableElement::ParseParams( PA_Tag *pTag, int16 csid ){ EDT_TableData *pData = NewData(); ED_Alignment malign; malign = edt_FetchParamAlignment( pTag, ED_ALIGN_DEFAULT, FALSE, csid ); // Center not supported in 3.0 if( malign == ED_ALIGN_RIGHT || malign == ED_ALIGN_LEFT || malign == ED_ALIGN_DEFAULT ){ pData->malign = malign; } pData->iRows = GetRows(); // Should we use CountRows()? NO THIS IS CALLED EVERY GetData call pData->iColumns = GetColumns(); // Should we use CountColumns()? // If you just say BORDER, it's the same as BORDER=1 // If you say BORDER=0, it's different than not saying BORDER at all pData->bBorderWidthDefined = edt_FetchParamBoolExist(pTag, PARAM_BORDER, csid); pData->iBorderWidth = edt_FetchParamInt(pTag, PARAM_BORDER, 0, 1, csid); pData->iCellSpacing = edt_FetchParamInt(pTag, PARAM_CELLSPACE, 1, csid); pData->iCellPadding = edt_FetchParamInt(pTag, PARAM_CELLPAD, 1, csid); { // If we're parsing a table tag, and we've hit a cols= property, we // need to remember the number of columns. int columns = edt_FetchParamInt(pTag, PARAM_COLS, 0, csid); pData->bUseCols = columns > 0; if ( pData->bUseCols ) { pData->iColumns = columns; } } pData->bWidthDefined = edt_FetchDimension( pTag, PARAM_WIDTH, &pData->iWidth, &pData->bWidthPercent, 100, TRUE, csid ); pData->bHeightDefined = edt_FetchDimension( pTag, PARAM_HEIGHT, &pData->iHeight, &pData->bHeightPercent, 100, TRUE, csid ); // If width and/or height are NOT defined, use the // "bPercent" values set by any previous SetData() calls if( !pData->bWidthDefined ) pData->bWidthPercent = m_bWidthPercent; if( !pData->bHeightDefined ) pData->bHeightPercent = m_bHeightPercent; pData->pColorBackground = edt_MakeLoColor(edt_FetchParamColor( pTag, PARAM_BGCOLOR, csid )); pData->pBackgroundImage = edt_FetchParamString( pTag, PARAM_BACKGROUND, csid ); pData->bBackgroundNoSave = edt_FetchParamBoolExist( pTag, PARAM_NOSAVE, csid ); pData->pExtra = edt_FetchParamExtras( pTag, tableParams, csid ); return pData; } EDT_TableData* CEditTableElement::NewData(){ EDT_TableData *pData = XP_NEW( EDT_TableData ); if( pData == 0 ){ // throw(); return pData; } pData->align = ED_ALIGN_LEFT; pData->malign = ED_ALIGN_DEFAULT; // While this helps layout, it causes lots of problems // in layout when COLSPAN > 1 an ROWSPAN > 1 pData->bUseCols = FALSE; pData->iRows = 1; pData->iColumns = 1; pData->bBorderWidthDefined = TRUE; pData->iBorderWidth = ED_DEFAULT_TABLE_BORDER; pData->iCellSpacing = ED_DEFAULT_CELL_SPACING; pData->iCellPadding = ED_DEFAULT_CELL_PADDING; pData->bWidthDefined = FALSE; pData->bWidthPercent = FALSE; // Percent mode is a pain pData->iWidth = 1; pData->iWidthPixels = 1; pData->bHeightDefined = FALSE; pData->bHeightPercent = FALSE; pData->iHeight = 1; pData->iHeightPixels = 1; pData->pColorBackground = 0; pData->pBackgroundImage = 0; pData->bBackgroundNoSave = FALSE; pData->pExtra = 0; return pData; } void CEditTableElement::FreeData( EDT_TableData *pData ){ if( pData->pColorBackground ) XP_FREE( pData->pColorBackground ); if( pData->pBackgroundImage ) XP_FREE( pData->pBackgroundImage ); if( pData->pExtra ) XP_FREE( pData->pExtra ); XP_FREE( pData ); } void CEditTableElement::SetBackgroundColor( ED_Color iColor ){ m_backgroundColor = iColor; } ED_Color CEditTableElement::GetBackgroundColor(){ return m_backgroundColor; } void CEditTableElement::SetBackgroundImage( char* pImage ){ delete m_pBackgroundImage; m_pBackgroundImage = 0; if ( pImage ) { m_pBackgroundImage = XP_STRDUP(pImage); } } char* CEditTableElement::GetBackgroundImage(){ return m_pBackgroundImage; } void CEditTableElement::PrintOpen( CPrintState *pPrintState ){ pPrintState->m_iCharPos = 0; pPrintState->m_pOut->Printf( "\n"); PA_Tag *pTag = InternalTagOpen(0, TRUE); while( pTag ){ char *pData = 0; if( pTag->data ){ PA_LOCK( pData, char*, pTag->data ); } if( pData && *pData != '>' ) { pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "<%s %s", EDT_TagString(pTag->type), pData ); } else { pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "<%s>", EDT_TagString(pTag->type)); } if( pTag->data ){ PA_UNLOCK( pTag->data ); } PA_Tag* pDelTag = pTag; pTag = pTag->next; PA_FreeTag(pDelTag); } } void CEditTableElement::PrintEnd( CPrintState *pPrintState ){ PA_Tag *pTag = TagEnd(); while( pTag ){ char *pData = 0; if( pTag->data ){ PA_LOCK( pData, char*, pTag->data ); } pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "", EDT_TagString( pTag->type ) ); if( pTag->data ){ PA_UNLOCK( pTag->data ); } PA_Tag* pDelTag = pTag; pTag = pTag->next; PA_FreeTag(pDelTag); } pPrintState->m_iCharPos = 0; pPrintState->m_pOut->Printf( "\n"); } LO_TableStruct* CEditTableElement::GetLoTable() { // Find the first leaf element in the table since // it will have pointer to LO_Element CEditElement *pLeaf = FindNextElement(&CEditElement::FindLeafAll,0 ); if( pLeaf ) { // Only leaf edit elements have their pointers saved in LO_Elements LO_Element *pLoElement = pLeaf->Leaf()->GetLayoutElement(); if( pLoElement ) { // Find enclosing LO cell LO_TableStruct *pLoTable = lo_GetParentTable(GetEditBuffer()->m_pContext, pLoElement); // We shoul ALWAYS find a table XP_ASSERT(pLoTable); return pLoTable; } } return NULL; } void CEditTableElement::GetParentSize(MWContext *pContext, int32 *pWidth, int32 *pHeight, LO_TableStruct *pLoTable) { // This may be supplied -- get it if not if( !pLoTable ) pLoTable = GetLoTable(); int32 iParentWidth = 1; int32 iParentHeight = 1; if( pLoTable ) { // Get viewing window int32 iXOrigin, iYOrigin; FE_GetDocAndWindowPosition(pContext, &iXOrigin, &iYOrigin, &iParentWidth, &iParentHeight); // Get extra margin int32 iMarginWidth; int32 iMarginHeight; LO_GetDocumentMargins(pContext, &iMarginWidth, &iMarginHeight); // Check for embeded table - get max width from Containing cell LO_CellStruct *pParentCell = lo_GetParentCell( pContext, (LO_Element*)pLoTable ); if( pParentCell ) { iParentWidth = pParentCell->width - (2 * pParentCell->border_horiz_space); iParentHeight = pParentCell->height - (2 * pParentCell->border_vert_space); } else { iParentWidth -= 2 * iMarginWidth; iParentHeight -= 2 * iMarginHeight; } } if( pWidth ) *pWidth = iParentWidth; if( pHeight ) *pHeight = iParentHeight; } void CEditTableElement::SetSizeMode(MWContext *pContext, int iMode) { int32 iParentWidth, iParentHeight; XP_Bool bCellPercent = (iMode & ED_MODE_CELL_PERCENT) ? TRUE : FALSE; XP_Bool bTablePercent = (iMode & ED_MODE_TABLE_PERCENT) ? TRUE : FALSE; XP_Bool bCellPixels = (iMode & ED_MODE_CELL_PIXELS) ? TRUE : FALSE; XP_Bool bTablePixels = (iMode & ED_MODE_TABLE_PIXELS) ? TRUE : FALSE; XP_Bool bUseCols = (iMode & ED_MODE_USE_COLS) ? TRUE : FALSE; XP_Bool bNoCols = (iMode & ED_MODE_NO_COLS) ? TRUE : FALSE; XP_Bool bChanged = FALSE; XP_Bool bSetTableWidth = (iMode & ED_MODE_USE_TABLE_WIDTH) ? TRUE : FALSE; XP_Bool bClearTableWidth = (iMode & ED_MODE_NO_TABLE_WIDTH) ? TRUE : FALSE; XP_Bool bSetTableHeight = (iMode & ED_MODE_USE_TABLE_HEIGHT) ? TRUE : FALSE; XP_Bool bClearTableHeight = (iMode & ED_MODE_NO_TABLE_HEIGHT) ? TRUE : FALSE; XP_Bool bSetCellWidth = (iMode & ED_MODE_USE_CELL_WIDTH) ? TRUE : FALSE; XP_Bool bClearCellWidth = (iMode & ED_MODE_NO_CELL_WIDTH) ? TRUE : FALSE; XP_Bool bSetCellHeight = (iMode & ED_MODE_USE_CELL_HEIGHT) ? TRUE : FALSE; XP_Bool bClearCellHeight = (iMode & ED_MODE_NO_CELL_HEIGHT) ? TRUE : FALSE; //XP_TRACE(("SetSizeMode: bPixels=%d, bPercent=%d, bSetCellWidth=%d, bClearCellWidth=%d", bPixels, bPercent, bSetCellWidth, bClearCellWidth)); // Save current values to restore after resizing m_bSaveWidthPercent = m_bWidthPercent; m_bSaveHeightPercent = m_bHeightPercent; // First set values for the table if( (bTablePixels && m_bWidthPercent) || (bTablePercent && !m_bWidthPercent) || (bTablePixels && m_bHeightPercent) || (bTablePercent && !m_bHeightPercent) || bUseCols || bNoCols || bSetTableWidth || bSetTableHeight || bClearTableWidth || bClearTableHeight ) { EDT_TableData *pTableData = GetData(); if( !pTableData ) return; // Save these to restore after relayout of table m_bSaveWidthDefined = pTableData->bWidthDefined; m_bSaveHeightDefined = pTableData->bHeightDefined; m_bSaveWidthPercent = pTableData->bWidthPercent; m_bSaveHeightPercent = pTableData->bHeightPercent; GetParentSize(pContext, &iParentWidth, &iParentHeight); if( (pTableData->bWidthDefined && bClearTableWidth) || (!pTableData->bWidthDefined && bSetTableWidth) ) { pTableData->bWidthDefined = bSetTableWidth; } if( (pTableData->bHeightDefined && bClearTableHeight) || (!pTableData->bHeightDefined && bSetTableHeight) ) { pTableData->bHeightDefined = bSetTableHeight; } if( (bTablePixels && pTableData->bWidthPercent) || (bTablePercent && !pTableData->bWidthPercent) ) { if( bTablePercent ) { pTableData->bWidthPercent = TRUE; pTableData->iWidth = m_iWidthPixels * 100 / iParentWidth; } else if( bTablePixels ){ pTableData->bWidthPercent = FALSE; pTableData->iWidth = m_iWidthPixels; } } if( (bTablePixels && pTableData->bHeightPercent) || (bTablePercent && !pTableData->bHeightPercent) ) { pTableData->bHeightPercent = bTablePercent; if( bTablePercent ) { pTableData->bHeightPercent = TRUE; pTableData->iHeight = m_iHeightPixels * 100 / iParentHeight; } else if( bTablePixels ){ pTableData->bHeightPercent = FALSE; pTableData->iHeight = m_iHeightPixels; } } if( bUseCols ) pTableData->bUseCols = TRUE; if( bNoCols ) pTableData->bUseCols = FALSE; SetData(pTableData); EDT_FreeTableData(pTableData); } // Process cells only if asked to set or clear the width or height if( bCellPixels || bCellPercent || bSetCellWidth || bClearCellWidth || bSetCellHeight || bClearCellHeight ) { // Scan through entire table to set all cells CEditTableCellElement *pCell = GetFirstCell(); if( pCell ) { // Get current width and height // (doesn't include amount we are changing // during current resizing) iParentWidth = pCell->GetParentWidth(); iParentHeight = pCell->GetParentHeight(); } while( pCell ) { // Only set cell's data if we change from current settings bChanged = FALSE; EDT_TableCellData *pCellData = pCell->GetData(); if( pCellData ) { if( (pCellData->bWidthDefined && bClearCellWidth) || (!pCellData->bWidthDefined && bSetCellWidth) ) { pCellData->bWidthDefined = bSetCellWidth; bChanged = TRUE; } if( (pCellData->bHeightDefined && bClearCellHeight) || (!pCellData->bHeightDefined && bSetCellHeight) ) { pCellData->bHeightDefined = bSetCellHeight; bChanged = TRUE; } // Save the current m_bWidthPercent, m_bHeightPercent, bWidthDefined, and bHeightDefined pCell->SaveSizeMode(pCellData->bWidthDefined, pCellData->bHeightDefined); if( bSetTableWidth && (bCellPixels && pCellData->bWidthPercent || bCellPercent && !pCellData->bWidthPercent) ) { if( bCellPercent ) { pCellData->bWidthPercent = TRUE; pCellData->iWidth = (pCellData->iWidthPixels * 100) / iParentWidth; } else { pCellData->bWidthPercent = FALSE; pCellData->iWidth = pCellData->iWidthPixels; } bChanged = TRUE; } if( bSetTableHeight && (bCellPixels && pCellData->bHeightPercent || bCellPercent && !pCellData->bHeightPercent) ) { if( bCellPercent ) { pCellData->bHeightPercent = TRUE; pCellData->iHeight = (pCellData->iHeightPixels * 100) / iParentHeight; } else { pCellData->bHeightPercent = FALSE; pCellData->iHeight = pCellData->iHeightPixels; } bChanged = TRUE; } if( bChanged ) pCell->SetData(pCellData); EDT_FreeTableCellData(pCellData); } pCell = GetNextCellInTable(); } } } void CEditTableElement::RestoreSizeMode(MWContext *pContext) { int32 iParentWidth; int32 iParentHeight; EDT_TableData *pTableData = GetData(); if( pTableData && pContext ) { // First restore the size mode for the table GetParentSize(pContext, &iParentWidth, &iParentHeight); if( m_bSaveWidthPercent ) { // Convert existing Pixel value to % of parent size pTableData->iWidth = (m_iWidthPixels * 100) / iParentWidth; pTableData->iHeight = (m_iHeightPixels * 100) / iParentHeight; } else { // Converting to pixels is simple - just use the pixel value we already have pTableData->iWidth = m_iWidthPixels; pTableData->iHeight = m_iHeightPixels; } pTableData->bWidthDefined = m_bSaveWidthDefined; pTableData->bHeightDefined = m_bSaveHeightDefined; pTableData->bWidthPercent = m_bSaveWidthPercent; pTableData->bHeightPercent = m_bSaveHeightPercent; SetData(pTableData); EDT_FreeTableData(pTableData); // Scan through entire table to reset all cells CEditTableCellElement *pCell = GetFirstCell(); if( pCell ) { iParentWidth = pCell->GetParentWidth(); iParentHeight = pCell->GetParentHeight(); } while( pCell ) { pCell->RestoreSizeMode(iParentWidth, iParentHeight); pCell = GetNextCellInTable(); } } } // This should only be called before laying out a table and // we expect to call AddLayoutData soon after to rebuild layout data and counters void CEditTableElement::DeleteLayoutData() { // Blank out current arrays and reset row and column counters memset(m_pColumnLayoutData, 0, m_iColumnLayoutSize*sizeof(EDT_CellLayoutData)); memset(m_pRowLayoutData, 0, m_iRowLayoutSize*sizeof(EDT_CellLayoutData)); m_iColumns = 0; m_iRows = 0; } // Store data for first cell in each column and row in growable arrays void CEditTableElement::AddLayoutData(CEditTableCellElement *pEdCell, LO_Element *pLoCell) { if( !pEdCell || !pLoCell ) return; EDT_CellLayoutData *pColData; EDT_CellLayoutData *pRowData; intn i; // We always add the cell the first time here XP_Bool bNewColumn = m_iColumns == 0; XP_Bool bNewRow = m_iRows == 0; for( i = 0; i < m_iColumns; i++ ) { if( pLoCell->lo_cell.x == m_pColumnLayoutData[i].X ) { // We already have the first cell in this column // Just increase the column count // Note: We count geometric columns, so we must look at COLSPAN m_pColumnLayoutData[i].iCellsInColumn += lo_GetColSpan(pLoCell); break; } else if( pLoCell->lo_cell.x > m_pColumnLayoutData[i].X && ( i+1 == m_iColumns || pLoCell->lo_cell.x < m_pColumnLayoutData[i+1].X ) ) { // We are at the last existing cell in array or // this cell's X is between current cell's and // the next cell in array // so we have a new column to insert between existing columns bNewColumn = TRUE; // The index to insert at is the NEXT location i++; break; } } if( bNewColumn ) { if( m_iColumns >= m_iColumnLayoutSize ) { m_pColumnLayoutData = (EDT_CellLayoutData*)XP_REALLOC(m_pColumnLayoutData, (LAYOUT_ARRAY_SIZE+m_iColumnLayoutSize)*sizeof(EDT_CellLayoutData)); if( m_pColumnLayoutData ) { m_iColumnLayoutSize += LAYOUT_ARRAY_SIZE; } else { m_iColumnLayoutSize = 0; XP_TRACE(("Out of memory for ColumnLayout data")); XP_ASSERT(FALSE); return; } } // Make room for inserted struct if not at end of current array if( i < m_iColumns ) XP_MEMMOVE(&m_pColumnLayoutData[i+1], &m_pColumnLayoutData[i], (m_iColumns-i)*sizeof(EDT_CellLayoutData)); pColData = &m_pColumnLayoutData[i]; pColData->X = pLoCell->lo_cell.x; pColData->Y = pLoCell->lo_cell.y; pColData->pEdCell = pEdCell; pColData->pLoCell = pLoCell; pColData->iColumn = i; pColData->iCellsInColumn = lo_GetColSpan(pLoCell); // These are ignored for column data pColData->iCellsInRow = pColData->iRow = 0; m_iColumns++; } for( i = 0; i < m_iRows; i++ ) { if( pLoCell->lo_cell.y == m_pRowLayoutData[i].Y ) { // We already have the first cell in this row // Just increase the row count m_pRowLayoutData[i].iCellsInRow += lo_GetRowSpan(pLoCell); break; } else if( pLoCell->lo_cell.y > m_pRowLayoutData[i].Y && ( i+1 == m_iRows || pLoCell->lo_cell.y < m_pRowLayoutData[i+1].Y ) ) { // We are at the last existing cell in array or // this cell's Y is between current cell's and // the next cell in array, // so we have a new row to insert between existing rows bNewRow = TRUE; // The index to insert at is the NEXT location i++; break; } } if( bNewRow ) { if( m_iRows >= m_iRowLayoutSize ) { m_pRowLayoutData = (EDT_CellLayoutData*)XP_REALLOC(m_pRowLayoutData, (LAYOUT_ARRAY_SIZE+m_iRowLayoutSize)*sizeof(EDT_CellLayoutData)); if( m_pRowLayoutData ) { m_iRowLayoutSize += LAYOUT_ARRAY_SIZE; } else { m_iRowLayoutSize = 0; XP_TRACE(("Out of memory for RowLayout data")); XP_ASSERT(FALSE); return; } } // Make room for inserted struct if not at end of current array if( i < m_iRows ) XP_MEMMOVE(&m_pRowLayoutData[i+1], &m_pRowLayoutData[i], (m_iRows-i)*sizeof(EDT_CellLayoutData)); pRowData = &m_pRowLayoutData[i]; pRowData->X = pLoCell->lo_cell.x; pRowData->Y = pLoCell->lo_cell.y; pRowData->pEdCell = pEdCell; pRowData->pLoCell = pLoCell; pRowData->iRow = i; pRowData->iCellsInRow = lo_GetRowSpan(pLoCell); // These are ignored for row data pRowData->iCellsInColumn = pRowData->iColumn = 0; m_iRows++; //m_pRowLayoutData.Insert(pRowData, i); } } // class CEditTableRowElement CEditTableRowElement::CEditTableRowElement() : CEditElement((CEditElement*) NULL, P_TABLE_ROW), m_backgroundColor(), m_iBackgroundSaveIndex(0), m_pBackgroundImage(0) { } CEditTableRowElement::CEditTableRowElement(intn columns) : CEditElement((CEditElement*) NULL, P_TABLE_ROW), m_backgroundColor(), m_iBackgroundSaveIndex(0), m_pBackgroundImage(0) { EDT_TRY { for ( intn column = 0; column < columns; column++ ) { CEditTableCellElement* pCell = new CEditTableCellElement(); pCell->InsertAsFirstChild(this); } } EDT_CATCH_ALL { Finalize(); EDT_RETHROW; } } CEditTableRowElement::CEditTableRowElement(CEditElement *pParent, PA_Tag *pTag, int16 /*csid*/) : CEditElement(pParent, P_TABLE_ROW), m_backgroundColor(), m_iBackgroundSaveIndex(0), m_pBackgroundImage(0) { if( pTag ){ char *locked_buff; PA_LOCK(locked_buff, char *, pTag->data ); if( locked_buff ){ SetTagData( locked_buff ); } PA_UNLOCK(pTag->data); } } CEditTableRowElement::CEditTableRowElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer) : CEditElement(pStreamIn, pBuffer), m_backgroundColor(), m_iBackgroundSaveIndex(0), m_pBackgroundImage(0) { } CEditTableRowElement::~CEditTableRowElement(){ delete m_pBackgroundImage; } CEditTableRowElement *CEditTableRowElement::GetNextRow() { CEditElement *pRow = GetNextSibling(); if( pRow && pRow->IsTableRow() ) { return pRow->TableRow(); } else if( pRow ) { // Most likely we have a caption XP_ASSERT(pRow->IsCaption()); // Can only have 1, so next one must // be a row or we're at the end already pRow = pRow->GetNextSibling(); if( pRow && pRow->IsTableRow() ) return pRow->TableRow(); } return NULL; } CEditTableCellElement *CEditTableRowElement::GetFirstCell() { CEditElement *pChild = GetChild(); if( pChild && pChild->IsTableCell() ) { return pChild->TableCell(); } return NULL; } // Scan all cells in row and add up m_iColSpan to get // maximum possible cells in the row intn CEditTableRowElement::GetCells() { intn cells = 0; // Get the first cell of the row CEditElement* pChild = GetChild(); if( pChild && pChild->IsTableCell() ) { // Starting number of cells is normally 0, // except when ROWSPAN causes first cell to be indented while( pChild ) { if ( pChild->IsTableCell() ) { // We must add column span to get maximum possible columns per row cells += ((CEditTableCellElement*)pChild)->GetColSpan(); } pChild = pChild->GetNextSibling(); } } return cells; } XP_Bool CEditTableRowElement::AllCellsInRowAreEmpty() { CEditTableCellElement *pCell = GetFirstCell(); if( !pCell || !pCell->IsTableCell() ) // Unlikely { XP_ASSERT(FALSE); return FALSE; } while( pCell && pCell->IsTableCell() ) { if( !pCell->IsEmpty() ) return FALSE; pCell = (CEditTableCellElement*)(pCell->GetNextSibling()); } return TRUE; } void CEditTableRowElement::FinishedLoad( CEditBuffer* pBuffer ){ // Wrap anything that's not a table cell in a table cell. CEditTableCellElement* pCell = NULL; CEditElement* pNext = 0; if ( ! GetChild() ) { // Force a cell CEditTableCellElement* pTempCell = new CEditTableCellElement(); pTempCell->InsertAsFirstChild(this); } for ( CEditElement* pChild = GetChild(); pChild; pChild = pNext) { pNext = pChild->GetNextSibling(); // We might unlink pChild if ( ! IsAcceptableChild(*pChild) ){ if ( ! pCell ){ pCell = new CEditTableCellElement(); pCell->InsertAfter(pChild); } pChild->Unlink(); pChild->InsertAsLastChild(pCell); } else { if ( pCell ){ pCell->FinishedLoad(pBuffer); pCell = NULL; } } pChild->FinishedLoad(pBuffer); } if ( pCell ){ pCell->FinishedLoad(pBuffer); pCell = NULL; } } static void edt_InsertCells(intn number, XP_Bool bAfter, CEditTableCellElement *pExisting, CEditTableRowElement *pSourceRow, CEditTableCellElement **ppCellForInsertPoint) { XP_ASSERT(pExisting); if( !pExisting ) return; CEditBuffer *pBuffer = pExisting->GetEditBuffer(); if( !pBuffer ) return; // Insert new cells at new insert position for ( intn col = 0; col < number; col++ ) { CEditTableCellElement *pInsertCell = NULL; if ( pSourceRow ) { // Remove cells from source from left to right pInsertCell = pSourceRow->GetFirstCell(); if( pInsertCell ) { // We found a cell - unlink it from source row // Note: When pasting a column, we get a NULL cell // if any cell in the column has COLSPAN > 1. // The cells without colspan will be "missing" in the source pInsertCell->Unlink(); // If inserted cell spans > 1 column, then // we should insert fewer to keep table rectangular intn iColSpan = pInsertCell->GetColSpan(); if( iColSpan > 1 ) number -= iColSpan - 1; } } // We didn't find a cell in source // (no source or because of COLSPAN > 1), // so insert a blank cell instead if( !pInsertCell ) pInsertCell = new CEditTableCellElement(); if( pInsertCell ) { if( bAfter ) { pInsertCell->InsertAfter(pExisting); // To insert new cells when pSourceRow is used // and we want to insert from left to right, // set the "before" cell to the one just inserted pExisting = pInsertCell; } else { // Insert before the current column // This will add pSourceRow cells from left to right pInsertCell->InsertBefore(pExisting); } // Be sure cell has the empty text element in default paragraph container, // but check if text should be a space pBuffer->SetFillNewCellWithSpace(); pInsertCell->FinishedLoad(pBuffer); pBuffer->ClearFillNewCellWithSpace(); } // Return the cell inserted if not already set if( ppCellForInsertPoint && *ppCellForInsertPoint == NULL ) *ppCellForInsertPoint = pInsertCell; } } // Note: iNewX should either be == X (insert before current column) // or == X + (insert column's width). void CEditTableRowElement::InsertCells(int32 X, int32 iNewX, intn number, CEditTableRowElement* pSourceRow, CEditTableCellElement **ppCellForInsertPoint) { if ( number <= 0 || X < 0 ) { return; } CEditTableElement *pTable = (CEditTableElement*)GetParent(); if( !pTable || !pTable->IsTable() ) return; CEditTableCellElement *pCellForInsertPoint = NULL; int32 iInterCellSpace = pTable->GetCellSpacing(); int32 iCellX; if ( pTable->GetColumnX(0) == iNewX ) { // Insert at the start. for ( intn col = 0; col < number; col++ ) { CEditTableCellElement* pInsertCell = NULL; if ( pSourceRow ) { // Insert cells from the end pInsertCell = (CEditTableCellElement*)pSourceRow->GetLastChild(); if( pInsertCell ) { pInsertCell->Unlink(); // If inserted cell spans > 1 column, then // we should insert fewer to keep table rectangular intn iColSpan = pInsertCell->GetColSpan(); if( iColSpan > 1 ) number -= iColSpan - 1; } } //Note: Create a new cell if no source or source row was incomplete if( !pInsertCell ) { pInsertCell = new CEditTableCellElement(); // Insert space into initial text CEditBuffer *pBuffer = GetEditBuffer(); pBuffer->SetFillNewCellWithSpace(); pInsertCell->FinishedLoad(pBuffer); pBuffer->ClearFillNewCellWithSpace(); } if( pInsertCell ) { // Save first cell inserted if( !pCellForInsertPoint ) pCellForInsertPoint = pInsertCell; pInsertCell->InsertAsFirstChild(this); } } } else { // Get the cell at the requested column CEditTableCellElement* pExisting = FindCell(X, X != iNewX); // This may now be NULL for "missing" cells because // of ROWSPAN or COLSPAN effects if( pExisting ) { iCellX = pExisting->GetX(); if( (iCellX < X || X != iNewX) && ((iCellX + pExisting->GetFullWidth()) > iNewX) ) // WAS GetWidth() { // Cell extends past new column insert point, // so simply increase span and don't add new cell pExisting->IncreaseColSpan(number); } else { // Non-spanned cell found at current column, // so insert before (X==iNewX) or after that cell edt_InsertCells(number, X < iNewX, pExisting, pSourceRow, &pCellForInsertPoint); } } else if( (X != iNewX) && (pExisting = FindCell(iNewX/*+iInterCellSpace*/)) != NULL ) { // No cell in current column, but we found cell at NEW insert column // so put new cell(s) before this edt_InsertCells(number, FALSE, pExisting, pSourceRow, &pCellForInsertPoint); } else { // We didn't find a cell, either because of cells spanning row // from above, or this row needs new cell(s) at the end // First cell in current row CEditTableCellElement *pFirstCell = GetFirstCell(); if( !pFirstCell ) goto INSERT_CELLS_DONE; // This should be same as pFirstCell, but we need to setup // to find other cells in same geometric row CEditTableCellElement *pCell = pTable->GetFirstCellInRow(pFirstCell->GetY()); XP_ASSERT(pCell == pFirstCell); intn iCurrentRow = pFirstCell->GetRow(); CEditTableCellElement *pCellBefore = NULL; CEditTableCellElement *pCellAfter = NULL; while(pCell) { iCellX = pCell->GetX(); int32 iCellWidth = pCell->GetFullWidth(); // WAS GetWidth() if( iCellX == X ) { // We found a cell from another row that spans current row // and target column, so just increase its column span instead of inserting if( X != iNewX && (iCellX+iCellWidth) > iNewX ) { pCell->IncreaseColSpan(number); goto INSERT_CELLS_DONE; } } // Find the closest cells in current row // to our target column if( pCell->GetRow() == iCurrentRow ) { if( (iCellX + pCell->GetFullWidth()) <= X ) pCellBefore = pCell; if( iCellX >= X && !pCellAfter ) pCellAfter = pCell; } pCell = pTable->GetNextCellInRow(); } if( pCellAfter ) { // FALSE = Force inserting before the cell found edt_InsertCells(number, FALSE, pCellAfter, pSourceRow, &pCellForInsertPoint); goto INSERT_CELLS_DONE; } if( pCellBefore ) { // This is tough. We know we need to add cells, but how many? // We can't be sure we had enough before so new cells end up // at desired new column. // TODO: USE pTable->m_pColumnLayoutData to figure out how // many cells to add to bring us to desired X // TRUE = insert after the cell found edt_InsertCells(number, TRUE, pCellBefore, pSourceRow, &pCellForInsertPoint); } } } INSERT_CELLS_DONE: // Return the cell inserted if not already set if( ppCellForInsertPoint && *ppCellForInsertPoint == NULL ) *ppCellForInsertPoint = pCellForInsertPoint; } intn CEditTableRowElement::AppendRow( CEditTableRowElement *pAppendRow, XP_Bool bDeleteRow ) { if( !pAppendRow ) return 0; intn iColumnsAppended = 0; CEditTableCellElement *pAppendCell = pAppendRow->GetFirstCell(); while( pAppendCell ) { // Unlink each cell in source row and // append it to current row pAppendCell->Unlink(); pAppendCell->InsertAsLastChild(this); iColumnsAppended += pAppendCell->GetColSpan(); pAppendCell = pAppendRow->GetFirstCell(); } if( bDeleteRow ) delete pAppendRow; return iColumnsAppended; } // This only works for table rows already layed out // (has gone through CEditBuffer::FixupTableData() ) void CEditTableRowElement::PadRowWithEmptyCells( intn iColumns ) { CEditTableElement *pTable = (CEditTableElement*)GetParent(); if( !pTable || !pTable->IsTable() ) return; CEditTableCellElement *pCell = GetFirstCell(); CEditTableCellElement *pLastCell = NULL; // Use number of columns supplied, or get current number in table intn iTotalColumns = iColumns ? iColumns : pTable->GetColumns(); while( pCell ) { pLastCell = pCell; pCell = pCell->GetNextCellInLogicalRow(); } if( pLastCell ) { // Get which column the last cell is in intn iLastIndex = pTable->GetColumnIndex(pLastCell->GetX()); if( iLastIndex >= 0 ) { intn iExtraColumns = iTotalColumns - iLastIndex - 1; if( iExtraColumns > 0 ) edt_InsertCells( iExtraColumns, TRUE, pLastCell, NULL, NULL); } } } void CEditTableRowElement::DeleteCells(int32 X, intn number, CEditTableCellElement **ppCellForInsertPoint) { if ( number == 0 ) { return; /* A waste of time, but legal. */ } if ( number < 0 || X < 0 ) { /* Illegal. */ XP_ASSERT(FALSE); return; } CEditTableElement *pTable = (CEditTableElement*)GetParent(); XP_ASSERT(pTable && pTable->IsTable() ); CEditTableCellElement *pCellForInsertPoint = NULL; CEditTableCellElement* pCell = FindCell(X); CEditTableCellElement* pPrevCell; if( pTable && pCell ) { // Save the first cell that we will NOT be deleting if( X == pCell->GetX() ) { pPrevCell = (CEditTableCellElement*)pCell->GetPreviousSibling(); #ifdef DEBUG if( pPrevCell ) XP_ASSERT(pPrevCell->IsTableCell()); #endif } else // Cell spans delete X, so we will only be reducing cellspan pPrevCell = pCell; intn iRemaining = number; CEditTableCellElement *pNextCell = NULL; while( iRemaining > 0 ) { // Get next cell before we delete the one we're at pNextCell = pCell->GetNextCellInLogicalRow(); int32 iCellX = pCell->GetX(); int32 iColSpan = pCell->GetColSpan(); if( iCellX >= X ) { // Cell is in same column as delete column or to the right of it if( iColSpan <= iRemaining ) { // Simplest case - just delete one cell // Remove any selected cells from our lists // else relayout blows up // NULL means let function find the corresponding LO_CellStruct if( pCell->IsSelected() ) GetEditBuffer()->SelectCell(FALSE, NULL, pCell); delete pCell; iRemaining -= iColSpan; } else if( iColSpan > iRemaining ) { // Cell spans more than we want to delete, // so reduce span instead of actually deleting pCell->DecreaseColSpan(iRemaining); iRemaining = 0; // Since this cell is in the delete column (same X), // then we want it to look deleted, so empty contents pCell->DeleteContents(); } } else { // Start of cell is before delete column // (but end must be past delete column) // Figure out how many columns it spans before delete column. int32 iSpanAvailable = iColSpan - pTable->GetColumnsSpanned(iCellX, X); if( iSpanAvailable > 0 ) { // Decrease the column span instead of deleting intn iDecrease = min(iSpanAvailable,iRemaining); pCell->DecreaseColSpan(iDecrease); iRemaining -= iDecrease; // "Undefine" (1st param) the width since it is no longer // in the same column as before // Keep existing bWidthPercent, the last param = width // (it doesn't matter what it is now) pCell->SetWidth(FALSE, pCell->GetWidthPercent(), 1); // then we want to use the width of the } } // We are done if no more cells in row or nothing remaining to delete if( !pNextCell ) break; pCell = pNextCell; } // Cell for insert point is the first non-deleted cell // (which should end up where the first deleted cell was) // or the previous cell if we deleted to the end of row pCellForInsertPoint = pNextCell ? pNextCell : pPrevCell; } else { //TODO: WHAT TO DO IF WE DON'T FIND A CELL? // Only case this should fail is if number of cells in this row // is less than the column being deleted // So doing nothing is probably OK XP_TRACE(("No cell in column at X = %d", X)); } // Return the cell inserted if not already set if( ppCellForInsertPoint && *ppCellForInsertPoint == NULL ) *ppCellForInsertPoint = pCellForInsertPoint; } CEditTableCellElement* CEditTableRowElement::FindCell(int32 X, XP_Bool bIncludeRightEdge) { CEditTableCellElement* pCell = NULL; XP_Bool bQuitNextCycle = FALSE; for ( CEditElement* pChild = GetChild(); pChild; pChild = pChild->GetNextSibling()) { if ( pChild->IsTableCell() ) { int32 cellX = pChild->TableCell()->GetX(); int32 cellRight = cellX + pChild->TableCell()->GetFullWidth(); // WAS GetWidth() // Back up 1 pixel if we shouldn't match the right edge if( !bIncludeRightEdge ) cellRight--; if ( X >= cellX && X <= cellRight ) { if( pCell ) { return pChild->TableCell(); } else { // We are here when we first find a cell // Save it but continue 1 more cycle to see if next cell's // left edge matches - use that instead if it does // (This is needed when border = 0, then the // right edge of one cell = left edge of next) pCell = pChild->TableCell(); bQuitNextCycle = TRUE; continue; // Skip test just below } } if( bQuitNextCycle ) break; } } return pCell; } void CEditTableRowElement::SetData( EDT_TableRowData *pData ){ char *pNew = 0; if( pData->align != ED_ALIGN_DEFAULT ){ pNew = PR_sprintf_append( pNew, "ALIGN=%s ", EDT_AlignString(pData->align) ); } if( pData->valign != ED_ALIGN_DEFAULT ){ pNew = PR_sprintf_append( pNew, "VALIGN=%s ", EDT_AlignString(pData->valign) ); } if ( pData->pColorBackground ) { SetBackgroundColor(EDT_LO_COLOR(pData->pColorBackground)); pNew = PR_sprintf_append( pNew, "BGCOLOR=\"#%06lX\" ", GetBackgroundColor().GetAsLong() ); } else { SetBackgroundColor(ED_Color::GetUndefined()); } if ( pData->pBackgroundImage ) { SetBackgroundImage(pData->pBackgroundImage); pNew = PR_sprintf_append( pNew, "BACKGROUND=\"%s\" ", pData->pBackgroundImage ); } else { SetBackgroundImage(0); } if ( pData->bBackgroundNoSave) { pNew = PR_sprintf_append( pNew, "NOSAVE " ); } if( pData->pExtra ){ pNew = PR_sprintf_append( pNew, "%s ", pData->pExtra ); } if( pNew ){ pNew = PR_sprintf_append( pNew, ">" ); } SetTagData( pNew ); if ( pNew ) { free(pNew); } } EDT_TableRowData* CEditTableRowElement::GetData( ){ EDT_TableRowData *pRet; PA_Tag* pTag = TagOpen(0); pRet = ParseParams( pTag, GetWinCSID() ); PA_FreeTag( pTag ); return pRet; } static char *tableRowParams[] = { PARAM_ALIGN, PARAM_VALIGN, PARAM_BGCOLOR, PARAM_BACKGROUND, PARAM_NOSAVE, 0 }; EDT_TableRowData* CEditTableRowElement::ParseParams( PA_Tag *pTag, int16 csid ){ EDT_TableRowData* pData = NewData(); ED_Alignment align; align = edt_FetchParamAlignment( pTag, ED_ALIGN_DEFAULT, FALSE, csid ); if( align == ED_ALIGN_RIGHT || align == ED_ALIGN_LEFT || align == ED_ALIGN_ABSCENTER ){ pData->align = align; } align = edt_FetchParamAlignment( pTag, ED_ALIGN_DEFAULT, TRUE, csid ); if( align == ED_ALIGN_ABSTOP || align == ED_ALIGN_ABSBOTTOM || align == ED_ALIGN_ABSCENTER || align == ED_ALIGN_BASELINE ){ pData->valign = align; } pData->pColorBackground = edt_MakeLoColor(edt_FetchParamColor( pTag, PARAM_BGCOLOR, csid )); pData->pBackgroundImage = edt_FetchParamString( pTag, PARAM_BACKGROUND, csid ); pData->bBackgroundNoSave = edt_FetchParamBoolExist( pTag, PARAM_NOSAVE, csid ); pData->pExtra = edt_FetchParamExtras( pTag, tableRowParams, csid ); return pData; } EDT_TableRowData* CEditTableRowElement::NewData(){ EDT_TableRowData* pData = XP_NEW( EDT_TableRowData ); if( pData == 0 ){ // throw(); return pData; } pData->align = ED_ALIGN_DEFAULT; pData->valign = ED_ALIGN_TOP; // ED_ALIGN_DEFAULT; this is centered - stupid! pData->pColorBackground = 0; pData->pBackgroundImage = 0; pData->bBackgroundNoSave = FALSE; pData->pExtra = 0; return pData; } void CEditTableRowElement::FreeData( EDT_TableRowData *pData ){ if( pData->pColorBackground ) XP_FREE( pData->pColorBackground ); if( pData->pBackgroundImage ) XP_FREE( pData->pBackgroundImage ); if( pData->pExtra ) XP_FREE( pData->pExtra ); XP_FREE( pData ); } void CEditTableRowElement::SetBackgroundColor( ED_Color iColor ){ m_backgroundColor = iColor; } ED_Color CEditTableRowElement::GetBackgroundColor(){ return m_backgroundColor; } void CEditTableRowElement::SetBackgroundImage( char* pImage ){ delete m_pBackgroundImage; m_pBackgroundImage = 0; if ( pImage ) { m_pBackgroundImage = XP_STRDUP(pImage); } } char* CEditTableRowElement::GetBackgroundImage(){ return m_pBackgroundImage; } // class CEditCaptionElement CEditCaptionElement::CEditCaptionElement() : CEditSubDocElement((CEditElement*) NULL, P_CAPTION) { } CEditCaptionElement::CEditCaptionElement(CEditElement *pParent, PA_Tag *pTag, int16 /*csid*/) : CEditSubDocElement(pParent, P_CAPTION) { if( pTag ){ char *locked_buff; PA_LOCK(locked_buff, char *, pTag->data ); if( locked_buff ){ SetTagData( locked_buff ); } PA_UNLOCK(pTag->data); } } CEditCaptionElement::CEditCaptionElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer) : CEditSubDocElement(pStreamIn, pBuffer) { } void CEditCaptionElement::SetData( EDT_TableCaptionData *pData ){ char *pNew = 0; if( pData->align == ED_ALIGN_ABSBOTTOM ){ pNew = PR_sprintf_append( pNew, "ALIGN=%s ", EDT_AlignString(pData->align) ); } if( pData->pExtra ){ pNew = PR_sprintf_append( pNew, "%s ", pData->pExtra ); } if( pNew ){ pNew = PR_sprintf_append( pNew, ">" ); } SetTagData( pNew ); if ( pNew ) { free(pNew); } } EDT_TableCaptionData* CEditCaptionElement::GetData( ){ return GetData( GetWinCSID() ); } EDT_TableCaptionData* CEditCaptionElement::GetData( int16 csid ){ EDT_TableCaptionData* pRet; PA_Tag* pTag = TagOpen(0); pRet = ParseParams( pTag, csid ); PA_FreeTag( pTag ); return pRet; } static char *tableCaptionParams[] = { PARAM_ALIGN, 0 }; EDT_TableCaptionData* CEditCaptionElement::ParseParams( PA_Tag *pTag, int16 csid ){ EDT_TableCaptionData* pData = NewData(); pData->align = edt_FetchParamAlignment( pTag, ED_ALIGN_ABSTOP, FALSE, csid ); pData->pExtra = edt_FetchParamExtras( pTag, tableCaptionParams, csid ); return pData; } EDT_TableCaptionData* CEditCaptionElement::NewData(){ EDT_TableCaptionData* pData = XP_NEW( EDT_TableCaptionData ); if( pData == 0 ){ // throw(); return pData; } pData->align = ED_ALIGN_CENTER; pData->pExtra = 0; return pData; } void CEditCaptionElement::FreeData( EDT_TableCaptionData *pData ){ if( pData->pExtra ) XP_FREE( pData->pExtra ); XP_FREE( pData ); } // class CEditTableCellElement CEditTableCellElement::CEditTableCellElement() : CEditSubDocElement((CEditElement*) NULL, P_TABLE_DATA), m_backgroundColor(), m_iBackgroundSaveIndex(0), m_pBackgroundImage(0), m_iWidth(1), m_iWidthPixels(1), m_iHeight(1), m_iHeightPixels(1), m_bWidthPercent(FALSE), m_bHeightPercent(FALSE), m_bWidthDefined(FALSE), m_bHeightDefined(FALSE), m_iColSpan(1), m_iRowSpan(1), m_iRow(0), m_X(0), m_Y(0), m_pSaveParent(0), m_pSaveNext(0), m_bSaveWidthPercent(FALSE), m_bSaveHeightPercent(FALSE), m_bSaveWidthDefined(FALSE), m_bSaveHeightDefined(FALSE), m_bSelected(FALSE), m_bSpecialSelected(FALSE), m_bDeleted(FALSE) { } CEditTableCellElement::CEditTableCellElement(XP_Bool bIsHeader) : CEditSubDocElement((CEditElement*) NULL, bIsHeader ? P_TABLE_HEADER : P_TABLE_DATA), m_backgroundColor(), m_iBackgroundSaveIndex(0), m_pBackgroundImage(0), m_iWidth(1), m_iWidthPixels(1), m_iHeight(1), m_iHeightPixels(1), m_bWidthPercent(FALSE), m_bHeightPercent(FALSE), m_bWidthDefined(FALSE), m_bHeightDefined(FALSE), m_iColSpan(1), m_iRowSpan(1), m_iRow(0), m_X(0), m_Y(0), m_pSaveParent(0), m_pSaveNext(0), m_bSaveWidthPercent(FALSE), m_bSaveHeightPercent(FALSE), m_bSaveWidthDefined(FALSE), m_bSaveHeightDefined(FALSE), m_bSelected(FALSE), m_bSpecialSelected(FALSE), m_bDeleted(FALSE) { } CEditTableCellElement::CEditTableCellElement(CEditElement *pParent, PA_Tag *pTag, int16 /*csid*/) : CEditSubDocElement(pParent, pTag->type), m_backgroundColor(), m_iBackgroundSaveIndex(0), m_pBackgroundImage(0), m_iWidth(1), m_iWidthPixels(1), m_iHeight(1), m_iHeightPixels(1), m_bWidthPercent(FALSE), m_bHeightPercent(FALSE), m_bWidthDefined(FALSE), m_bHeightDefined(FALSE), m_iColSpan(1), m_iRowSpan(1), m_iRow(0), m_X(0), m_Y(0), m_pSaveParent(0), m_pSaveNext(0), m_bSaveWidthPercent(FALSE), m_bSaveHeightPercent(FALSE), m_bSaveWidthDefined(FALSE), m_bSaveHeightDefined(FALSE), m_bSelected(FALSE), m_bSpecialSelected(FALSE), m_bDeleted(FALSE) { if( pTag ){ char *locked_buff; PA_LOCK(locked_buff, char *, pTag->data ); if( locked_buff ){ SetTagData( locked_buff ); } PA_UNLOCK(pTag->data); } } CEditTableCellElement::CEditTableCellElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer) : CEditSubDocElement(pStreamIn, pBuffer), m_backgroundColor(), m_iBackgroundSaveIndex(0), m_pBackgroundImage(0), m_iWidth(1), m_iWidthPixels(1), m_iHeight(1), m_iHeightPixels(1), m_bWidthPercent(FALSE), m_bHeightPercent(FALSE), m_iColSpan(1), m_iRowSpan(1), m_iRow(0), m_X(0), m_Y(0), m_pSaveParent(0), m_pSaveNext(0), m_bSaveWidthPercent(FALSE), m_bSaveHeightPercent(FALSE), m_bSaveWidthDefined(FALSE), m_bSaveHeightDefined(FALSE), m_bSelected(FALSE), m_bSpecialSelected(FALSE), m_bDeleted(FALSE) { // We need this so we can accurately determine number // of columns in stream m_X = pStreamIn->ReadInt(); // Get width from stream -- needed for pasting tables/cells // We must get CSID from buffer since element is not in doc yet //TODO: Should we pass in CSID from paste stream instead? PA_Tag* pTag = CEditElement::TagOpen(0); EDT_TableCellData *pData = ParseParams( pTag, pBuffer->GetRAMCharSetID() ); if(pData) { m_bWidthDefined = pData->bWidthDefined; m_bHeightDefined = pData->bHeightDefined; // Save these since they are not saved in tag data if bWidthPercent = FALSE m_iWidth = pData->iWidth; m_bWidthPercent = pData->bWidthPercent; if( !m_bWidthPercent ) m_iWidthPixels = m_iWidth; m_iHeight = pData->iHeight; m_bHeightPercent = pData->bHeightPercent; if( !m_bHeightPercent ) m_iHeightPixels = m_iHeight; EDT_FreeTableCellData(pData); } PA_FreeTag( pTag ); } // We should never call Unlink for a table cell we are about // to delete - it will get unlinked automatically. // But in case someone does, we must unselect the cell here // else it will fail to find the EditBuffer in the destructor void CEditTableCellElement::Unlink(){ if( IsSelected() && GetEditBuffer() ) GetEditBuffer()->SelectCell(FALSE, NULL, this); CEditElement::Unlink(); } CEditTableCellElement::~CEditTableCellElement(){ // Be sure any cell deleted is not selected if( IsSelected() && GetEditBuffer() ) GetEditBuffer()->SelectCell(FALSE, NULL, this); delete m_pBackgroundImage; } XP_Bool CEditTableCellElement::IsTableCell(){ return TRUE; } EEditElementType CEditTableCellElement::GetElementType(){ return eTableCellElement; } ED_Alignment CEditTableCellElement::GetDefaultAlignment(){ return ED_ALIGN_DEFAULT; } XP_Bool CEditTableCellElement::IsTableData(){ return GetType() == P_TABLE_DATA; } void CEditTableCellElement::IncreaseColSpan(int32 iIncrease) { EDT_TableCellData *pData = GetData(); if( pData ) { pData->iColSpan += iIncrease; SetData(pData); EDT_FreeTableCellData(pData); } } void CEditTableCellElement::IncreaseRowSpan(int32 iIncrease) { EDT_TableCellData *pData = GetData(); if( pData ) { pData->iRowSpan += iIncrease; SetData(pData); EDT_FreeTableCellData(pData); } } void CEditTableCellElement::DecreaseColSpan(int32 iDecrease) { EDT_TableCellData *pData = GetData(); if( pData ) { pData->iColSpan = max(1, pData->iColSpan - iDecrease); SetData(pData); EDT_FreeTableCellData(pData); } } void CEditTableCellElement::DecreaseRowSpan(int32 iDecrease) { EDT_TableCellData *pData = GetData(); if( pData ) { pData->iRowSpan = max(1, pData->iRowSpan - iDecrease); SetData(pData); EDT_FreeTableCellData(pData); } } // Save size percent and defined params so we can much with them during resizing, // and restore them later. Supply bWidthDefined and bHeightDefined // for efficiency so we don't have to do GetData() here void CEditTableCellElement::SaveSizeMode(XP_Bool bWidthDefined, XP_Bool bHeightDefined) { m_bSaveWidthPercent = m_bWidthPercent; m_bSaveHeightPercent = m_bWidthPercent; m_bSaveWidthDefined = bWidthDefined; m_bSaveHeightDefined = bHeightDefined; } // This will restore saved width and height percent modes, // readjust m_bWidth an m_bHeight, and call SetData to set tag data // Note: no need to relayout -- we are just changing modes, // not actual size of cell void CEditTableCellElement::RestoreSizeMode(int32 iParentWidth, int32 iParentHeight) { EDT_TableCellData *pData = GetData(); if( iParentWidth == 0 ) iParentWidth = GetParentWidth(); if( iParentHeight == 0 ) iParentHeight = GetParentHeight(); if( pData ) { if( m_bSaveWidthPercent ) { // Convert existing Pixel value to % of parent size pData->iWidth = (m_iWidthPixels * 100) / iParentWidth; pData->iHeight = (m_iHeightPixels * 100) / iParentHeight; } else { // Converting to pixels is simple - just use the pixel value we already have pData->iWidth = m_iWidthPixels; pData->iHeight = m_iHeightPixels; } pData->bWidthDefined = m_bSaveWidthDefined; pData->bHeightDefined = m_bSaveHeightDefined; pData->bWidthPercent = m_bSaveWidthPercent; pData->bHeightPercent = m_bSaveHeightPercent; SetData(pData); EDT_FreeTableCellData(pData); } } // Calculate the Percent size from the iWidthPixels and iHeightPixels in pData void CEditTableCellElement::CalcPercentWidth(EDT_TableCellData *pData) { if( pData && pData->bWidthDefined ) { if( pData->bWidthPercent ) pData->iWidth = (pData->iWidthPixels * 100) / GetParentWidth(); else pData->iWidth = pData->iWidthPixels; } } void CEditTableCellElement::CalcPercentHeight(EDT_TableCellData *pData) { if( pData && pData->bHeightDefined ) { if( pData->bHeightPercent ) pData->iHeight = (pData->iHeightPixels * 100) / GetParentHeight(); else pData->iHeight = pData->iHeightPixels; } } // Calculate the Pixel size from the iWidth and iHeight in pData void CEditTableCellElement::CalcPixelWidth(EDT_TableCellData *pData) { if( pData && pData->bWidthDefined ) { if( pData->bWidthPercent ) pData->iWidthPixels = (pData->iWidth * GetParentWidth()) / 100; else pData->iWidthPixels = pData->iWidth; } } void CEditTableCellElement::CalcPixelHeight(EDT_TableCellData *pData) { if( pData && pData->bHeightDefined ) { if( pData->bHeightPercent ) pData->iHeightPixels = (pData->iHeight * GetParentHeight()) / 100; else pData->iHeightPixels = pData->iHeight; } } void CEditTableCellElement::SetSizeData(EDT_TableCellData * pData) { EDT_TableCellData * pCurrentData = GetData(0); if( pData && pCurrentData ) { pCurrentData->X = pData->X; pCurrentData->Y = pData->Y; pCurrentData->iRow = pData->iRow; pCurrentData->bWidthDefined = pData->bWidthDefined; pCurrentData->bWidthPercent = pData->bWidthPercent; pCurrentData->iWidthPixels = pData->iWidthPixels; CalcPercentWidth(pCurrentData); pCurrentData->bHeightDefined = pData->bHeightDefined; pCurrentData->bHeightPercent = pData->bHeightPercent; pCurrentData->iHeightPixels = pData->iHeightPixels; CalcPercentHeight(pCurrentData); SetData(pCurrentData); EDT_FreeTableCellData(pCurrentData); } } void CEditTableCellElement::SetWidth(XP_Bool bWidthDefined, XP_Bool bWidthPercent, int32 iWidthPixels) { EDT_TableCellData * pData = GetData(0); if( pData ) { pData->bWidthDefined = bWidthDefined; pData->bWidthPercent = bWidthPercent; pData->iWidthPixels = iWidthPixels; CalcPercentWidth(pData); SetData(pData); EDT_FreeTableCellData(pData); } } void CEditTableCellElement::SetHeight(XP_Bool bHeightDefined, XP_Bool bHeightPercent, int32 iHeightPixels) { EDT_TableCellData * pData = GetData(0); if( pData ) { pData->bHeightDefined = bHeightDefined; pData->bHeightPercent = bHeightPercent; pData->iHeightPixels = iHeightPixels; CalcPercentHeight(pData); SetData(pData); EDT_FreeTableCellData(pData); } } // Set width of supplied cell and all other cells sharing or spanning the same right edge // Also sets the bWidthDefined and bWidthPercent modes void CEditTableCellElement::SetColumnWidthRight(CEditTableElement *pTable, LO_Element *pLoCell, EDT_TableCellData *pData) { if( !pTable || !pLoCell || !pData || pData->iWidthPixels <= 0 ) return; // Get LoTable to start scanning through cells LO_Element *pLoElement = (LO_Element*)lo_GetParentTable(NULL, pLoCell); if( !pLoElement ) return; // Amount of change in supplied cell int32 iDeltaWidth = pData->iWidthPixels - pLoCell->lo_any.width; int32 iRightEdge = pLoCell->lo_cell.x + pLoCell->lo_cell.width; // Set the width for all cells sharing or spanning the right edge of cell resized while (pLoElement && pLoElement->type != LO_LINEFEED ) { if( pLoElement->type == LO_CELL && pLoElement->lo_cell.x < iRightEdge && (pLoElement->lo_cell.x + pLoElement->lo_cell.width) >= iRightEdge ) { CEditTableCellElement *pEdCell = (CEditTableCellElement*)edt_GetTableElementFromLO_Element( pLoElement, LO_CELL ); if( pEdCell ) { pLoElement->lo_cell.width = max(1, pLoElement->lo_cell.width + iDeltaWidth); // Compensate for cell padding, border, and COLSPAN to get // the value to use for WIDTH tag param pEdCell->SetWidth( pData->bWidthDefined, pData->bWidthPercent, lo_GetCellTagWidth(pLoElement) ); } } pLoElement = pLoElement->lo_any.next; } EDT_TableData *pTableData = pTable->GetData(); if( pTableData ) { if( pTableData->bWidthDefined ) { // Force pixel mode and adjust the width of parent table // If we don't, a table width in Percent mode will fight column size change pTableData->iWidthPixels = max(1,pTableData->iWidthPixels + iDeltaWidth); pTableData->bWidthPercent = FALSE; pTableData->iWidth = pTableData->iWidthPixels; pTable->SetData(pTableData); } EDT_FreeTableData(pTableData); } } // Set height of all cells sharing or spanning the bottom edge // Also sets the bWidthDefined and bWidthPercent modes void CEditTableCellElement::SetRowHeightBottom(CEditTableElement *pTable, LO_Element *pLoCell, EDT_TableCellData *pData) { if( !pTable || !pLoCell || !pData || pData->iHeightPixels <= 0 ) return; // Get LoTable to start scanning through cells LO_Element *pLoElement = (LO_Element*)lo_GetParentTable(NULL, pLoCell); if( !pLoElement ) return; // Amount of change in supplied cell - use this for all cells we will resize int32 iDeltaHeight = pData->iHeightPixels - pLoCell->lo_any.height; int32 iBottomEdge = pLoCell->lo_cell.y + pLoCell->lo_cell.height; // Set the height for all cells sharing or spanning the right edge of cell resized while (pLoElement && pLoElement->type != LO_LINEFEED ) { if( pLoElement->type == LO_CELL && pLoElement->lo_cell.y < iBottomEdge && (pLoElement->lo_cell.y + pLoElement->lo_cell.height) >= iBottomEdge ) { CEditTableCellElement *pEdCell = (CEditTableCellElement*)edt_GetTableElementFromLO_Element( pLoElement, LO_CELL ); if( pEdCell ) { pLoElement->lo_cell.height = max(1, pLoElement->lo_cell.height + iDeltaHeight); pEdCell->SetHeight( pData->bHeightDefined, pData->bHeightPercent, lo_GetCellTagHeight(pLoElement) ); } } pLoElement = pLoElement->lo_any.next; } } // Set width of supplied cell and all other cells sharing or spanning the same left edge // Also sets the bWidthDefined and bWidthPercent modes void CEditTableCellElement::SetColumnWidthLeft(CEditTableElement *pTable, CEditTableCellElement *pCell, EDT_TableCellData *pData) { if( !pTable || !pCell || !pData || pData->iWidthPixels <= 0 ) return; // Amount of change in supplied cell int32 iDeltaWidth = pData->iWidthPixels - pCell->GetWidth(); // TRUE means we include cells that span over cell's left edge, // not just start at that value. pCell = pCell->GetFirstCellInColumn(pCell->GetX(), TRUE); while( pCell ) { // Set width - be sure it is at least 1 pCell->SetWidth( pData->bWidthDefined, pData->bWidthPercent, max(1, pCell->GetWidth() + iDeltaWidth) ); pCell = pCell->GetNextCellInColumn(); } } // Set height of all cells sharing or spanning the top edge // Also sets the bWidthDefined and bWidthPercent modes void CEditTableCellElement::SetRowHeightTop(CEditTableElement *pTable, CEditTableCellElement *pCell, EDT_TableCellData *pData) { if( !pTable || !pCell || !pData || pData->iHeightPixels <= 0 ) return; // Amount of change in supplied cell - use this for all cells we will resize int32 iDeltaHeight = pData->iHeightPixels - pCell->GetHeight(); // TRUE means we include cells that span over current cell's Y, // not just start at Y pCell = pCell->GetFirstCellInRow(pCell->GetY(), TRUE); while( pCell ) { // Set height - be sure it is at least 1 pCell->SetHeight( pData->bHeightDefined, pData->bHeightPercent, max(1, pCell->GetHeight() + iDeltaHeight) ); pCell = pCell->GetNextCellInRow(); } } void CEditTableCellElement::StreamOut(IStreamOut *pOut){ CEditElement::StreamOut( pOut ); // We write our X so we can accurately determine // the number of columns when streaming in (pasting) pOut->WriteInt( m_X ); } void CEditTableCellElement::SetData( EDT_TableCellData *pData ){ // bHeader is actually stored as the tag data if ( pData->bHeader ) { SetType(P_TABLE_HEADER); } else { SetType(P_TABLE_DATA); } //TODO: Account for interaction between default align and Header style char *pNew = 0; if( pData->align != ED_ALIGN_DEFAULT ){ pNew = PR_sprintf_append( pNew, "ALIGN=%s ", EDT_AlignString(pData->align) ); } if( pData->valign != ED_ALIGN_DEFAULT ){ pNew = PR_sprintf_append( pNew, "VALIGN=%s ", EDT_AlignString(pData->valign) ); } if ( pData->iColSpan != 1 ){ pNew = PR_sprintf_append( pNew, "COLSPAN=\"%d\" ", pData->iColSpan ); } if ( pData->iRowSpan != 1 ){ pNew = PR_sprintf_append( pNew, "ROWSPAN=\"%d\" ", pData->iRowSpan ); } if ( pData->bNoWrap ){ pNew = PR_sprintf_append( pNew, "NOWRAP " ); } if( pData->bWidthDefined ){ if( pData->bWidthPercent ){ pNew = PR_sprintf_append( pNew, "WIDTH=\"%ld%%\" ", (long)min(100,max(1,pData->iWidth)) ); } else { pNew = PR_sprintf_append( pNew, "WIDTH=\"%ld\" ", (long)pData->iWidth ); } } if( pData->bHeightDefined ){ if( pData->bHeightPercent ){ pNew = PR_sprintf_append( pNew, "HEIGHT=\"%ld%%\" ", (long)min(100,max(1, pData->iHeight)) ); } else { pNew = PR_sprintf_append( pNew, "HEIGHT=\"%ld\" ", (long)pData->iHeight ); } } if ( pData->pColorBackground ) { SetBackgroundColor(EDT_LO_COLOR(pData->pColorBackground)); pNew = PR_sprintf_append( pNew, "BGCOLOR=\"#%06lX\" ", GetBackgroundColor().GetAsLong() ); } else { SetBackgroundColor(ED_Color::GetUndefined()); } if ( pData->pBackgroundImage ) { SetBackgroundImage(pData->pBackgroundImage); pNew = PR_sprintf_append( pNew, "BACKGROUND=\"%s\" ", pData->pBackgroundImage ); } else { SetBackgroundImage(0); } if ( pData->bBackgroundNoSave) { pNew = PR_sprintf_append( pNew, "NOSAVE " ); } if( pData->pExtra ){ pNew = PR_sprintf_append( pNew, "%s ", pData->pExtra ); } if( pNew ){ pNew = PR_sprintf_append( pNew, ">" ); } SetTagData( pNew ); if ( pNew ) { free(pNew); } // Save these since they are not saved in tag data if bWidthPercent = FALSE m_X = pData->X; m_Y = pData->Y; m_bWidthDefined = pData->bWidthDefined; m_bHeightDefined = pData->bHeightDefined; m_iWidthPixels = pData->iWidthPixels; m_iHeightPixels = pData->iHeightPixels; m_iWidth = pData->iWidth; m_bWidthPercent = pData->bWidthPercent; m_iHeight = pData->iHeight; m_bHeightPercent = pData->bHeightPercent; m_iRow = pData->iRow; m_iColSpan = pData->iColSpan; m_iRowSpan = pData->iRowSpan; } // Since Getting/Setting data is costly (lots of string manipulation) // use this when efficiency is important and you need just size data // For even more efficiency, supply the TableCellData to avoid // alloc/delete for large number of cells EDT_TableCellData *CEditTableCellElement::GetSizeData(EDT_TableCellData *pData) { if( pData == NULL ) { pData = XP_NEW( EDT_TableCellData ); if( pData ) XP_MEMSET( pData, 0, sizeof(EDT_TableCellData)); } if( pData == 0 ) return NULL; // Get just our member variables pData->bWidthDefined = m_bWidthDefined; pData->bHeightDefined = m_bHeightDefined; pData->bWidthPercent = m_bWidthPercent; pData->bHeightPercent = m_bHeightPercent; pData->iWidth = m_iWidth; pData->iWidthPixels = m_iWidthPixels; pData->iHeight = m_iHeight; pData->iHeightPixels = m_iHeightPixels; pData->X = m_X; pData->Y = m_Y; pData->iRow = m_iRow; return pData; } EDT_TableCellData* CEditTableCellElement::GetData( int16 csid ) { //TODO: Account for interaction between default align and Header style EDT_TableCellData *pRet; PA_Tag* pTag = TagOpen(0); // Must supply csid if cell is not already in the document if( csid == 0 ) csid = GetWinCSID(); // Get params stored in the HTML tag struct pRet = ParseParams( pTag, csid ); PA_FreeTag( pTag ); // Return the actual X, Y, width, and height // as determined by table layout algorithm // This is set correctly event if bWidthDefined = FALSE pRet->X = m_X; pRet->Y = m_Y; pRet->iRow = m_iRow; pRet->iWidth = m_iWidth; pRet->iWidthPixels = m_iWidthPixels; pRet->iHeight = m_iHeight; pRet->iHeightPixels = m_iHeightPixels; pRet->iRow = m_iRow; pRet->iColSpan = m_iColSpan; pRet->iRowSpan = m_iRowSpan; pRet->mask = -1; pRet->iSelectionType = ED_HIT_NONE; pRet->iSelectedCount = 0; return pRet; } // Include all borders, spacing, and padding up to the next cell int32 CEditTableCellElement::GetFullWidth(CEditTableElement *pTable) { if( !pTable ) pTable = GetParentTable(); if( !pTable ) return 0; return (m_iWidthPixels + 2*(pTable->GetCellBorder() + pTable->GetCellPadding()) + m_iColSpan * pTable->GetCellSpacing() ); } int32 CEditTableCellElement::GetFullHeight(CEditTableElement *pTable) { if( !pTable ) pTable = GetParentTable(); if( !pTable ) return 0; return (m_iHeightPixels + 2*(pTable->GetCellBorder() + pTable->GetCellPadding()) + m_iRowSpan * pTable->GetCellSpacing() ); } static XP_Bool edt_CompareLoColors( LO_Color *pLoColor1, LO_Color *pLoColor2 ) { // One or the other doesn't have color, // they are different if( (!pLoColor1 && pLoColor2) || (pLoColor1 && !pLoColor2 ) ) { return FALSE; } // Neither has a color - the same if( !pLoColor1 && !pLoColor2 ) return TRUE; // Any color is different if( pLoColor1->red != pLoColor2->red || pLoColor1->green != pLoColor2->green || pLoColor1->blue != pLoColor2->blue ) { return FALSE; } // All must be the same return TRUE; } static XP_Bool edt_CompareStrings( char *pString1, char *pString2, XP_Bool bFilenameCompare ) { // One or the other doesn't exist, // they are different if( (!pString1 && pString2) || (pString1 && !pString2 ) ) { return FALSE; } // Neither exists - the same if( !pString1 && !pString2 ) { return TRUE; } // Compare the strings if( bFilenameCompare ) { // This compare pays NO attention to case differences for Windows only return (0 == XP_FILENAMECMP(pString1, pString2)); } return (0 == XP_STRCMP(pString1, pString2)); } void CEditTableCellElement::MaskData( EDT_TableCellData *pData ) { if( !pData ) return; EDT_TableCellData *pCurrentData = GetData(); // Change data only for attributes whose bit is set in data mask if( (pData->mask & CF_ALIGN) && pCurrentData->align != pData->align ) { pData->mask &= ~CF_ALIGN; } if( (pData->mask & CF_VALIGN) && pCurrentData->valign != pData->valign ) { pData->mask &= ~CF_VALIGN; } if( (pData->mask & CF_COLSPAN) && pCurrentData->iColSpan != pData->iColSpan ) { pData->mask &= ~CF_COLSPAN; } if( (pData->mask & CF_ROWSPAN) && pCurrentData->iRowSpan != pData->iRowSpan ) { pData->mask &= ~CF_ROWSPAN; } if( (pData->mask & CF_HEADER) && pCurrentData->bHeader != pData->bHeader ) { pData->mask &= ~CF_HEADER; } if( (pData->mask & CF_NOWRAP) && pCurrentData->bNoWrap != pData->bNoWrap ) { pData->mask &= ~CF_NOWRAP; } if( (pData->mask & CF_BACK_NOSAVE) && pCurrentData->bBackgroundNoSave != pData->bBackgroundNoSave ) { pData->mask &= ~CF_BACK_NOSAVE; } if( (pData->mask & CF_WIDTH) && (pCurrentData->bWidthDefined != pData->bWidthDefined || pCurrentData->iWidth != pData->iWidth || pCurrentData->bWidthPercent != pData->bWidthPercent) ) { pData->mask &= ~CF_WIDTH; } if( (pData->mask & CF_HEIGHT) && (pCurrentData->bHeightDefined != pData->bHeightDefined || pCurrentData->iHeight != pData->iHeight || pCurrentData->bHeightPercent != pData->bHeightPercent) ) { pData->mask &= ~CF_HEIGHT; } if( (pData->mask & CF_BACK_COLOR) && !edt_CompareLoColors(pCurrentData->pColorBackground, pData->pColorBackground) ) { pData->mask &= ~CF_BACK_COLOR; } // This pays NO attention to case differences for Windows only if( (pData->mask & CF_BACK_IMAGE) && !edt_CompareStrings(pCurrentData->pBackgroundImage, pData->pBackgroundImage, TRUE) ) { pData->mask &= ~CF_BACK_IMAGE; } // This pays attention to case differences if( (pData->mask & CF_EXTRA_HTML) && !edt_CompareStrings(pCurrentData->pExtra, pData->pExtra, FALSE) ) { pData->mask &= ~CF_EXTRA_HTML; } } // Add up widths of all cells in the first row of table // This is available total width for "Percent of Table" calculation int32 CEditTableCellElement::GetParentWidth() { // Don't return 0 to avoid divide by zero errors int32 iParentWidth = 1; CEditTableElement *pTable = GetParentTable(); if( pTable ) { CEditTableCellElement *pCell = pTable->GetFirstCell(); while( pCell && pCell->IsTableCell() ) { iParentWidth += pCell->GetWidth(); // TODO: SHOULD WE USE GetFullWidth()??? pCell = (CEditTableCellElement*)(pCell->GetNextSibling()); } } return iParentWidth; } int32 CEditTableCellElement::GetParentHeight() { // Don't return 0 to avoid divide by zero errors int32 iParentHeight = 1; CEditTableElement *pTable = GetParentTable(); if( pTable ) { CEditTableCellElement *pCell = pTable->GetFirstCell(); while( pCell ) { iParentHeight += pCell->GetHeight(); pCell = pTable->GetNextCellInColumn(pCell); } } return iParentHeight; } static char *tableCellParams[] = { PARAM_ALIGN, PARAM_VALIGN, PARAM_COLSPAN, PARAM_ROWSPAN, PARAM_NOWRAP, PARAM_WIDTH, PARAM_HEIGHT, PARAM_BGCOLOR, PARAM_BACKGROUND, PARAM_NOSAVE, 0 }; EDT_TableCellData* CEditTableCellElement::ParseParams( PA_Tag *pTag, int16 csid ){ EDT_TableCellData *pData = NewData(); ED_Alignment align; align = edt_FetchParamAlignment( pTag, ED_ALIGN_DEFAULT, FALSE, csid ); if( align == ED_ALIGN_RIGHT || align == ED_ALIGN_LEFT || align == ED_ALIGN_ABSCENTER ){ pData->align = align; } align = edt_FetchParamAlignment( pTag, ED_ALIGN_DEFAULT, TRUE, csid ); if( align == ED_ALIGN_ABSTOP || align == ED_ALIGN_ABSBOTTOM || align == ED_ALIGN_ABSCENTER || align == ED_ALIGN_BASELINE ){ pData->valign = align; } pData->iColSpan = edt_FetchParamInt(pTag, PARAM_COLSPAN, 1, csid); pData->iRowSpan = edt_FetchParamInt(pTag, PARAM_ROWSPAN, 1, csid); m_iColSpan = pData->iColSpan; m_iRowSpan = pData->iRowSpan; pData->bHeader = GetType() == P_TABLE_HEADER; pData->bNoWrap = edt_FetchParamBoolExist(pTag, PARAM_NOWRAP, csid ); pData->bWidthDefined = edt_FetchDimension( pTag, PARAM_WIDTH, &pData->iWidth, &pData->bWidthPercent, 100, TRUE, csid ); pData->bHeightDefined = edt_FetchDimension( pTag, PARAM_HEIGHT, &pData->iHeight, &pData->bHeightPercent, 100, TRUE, csid ); // This and CEditBuffer::FixupTableData() are the only // places member variable should be set // If width and/or height are NOT defined, use the // "bPercent" values set by any previous SetData() calls if( pData->bWidthDefined ) m_iWidth = pData->iWidth; else pData->bWidthPercent = m_bWidthPercent; if( pData->bHeightDefined ) m_iHeight = pData->iHeight; else pData->bHeightPercent = m_bHeightPercent; pData->pColorBackground = edt_MakeLoColor(edt_FetchParamColor( pTag, PARAM_BGCOLOR, csid )); pData->pBackgroundImage = edt_FetchParamString( pTag, PARAM_BACKGROUND, csid ); pData->bBackgroundNoSave = edt_FetchParamBoolExist( pTag, PARAM_NOSAVE, csid ); pData->pExtra = edt_FetchParamExtras( pTag, tableCellParams, csid ); return pData; } EDT_TableCellData* CEditTableCellElement::NewData(){ EDT_TableCellData *pData = XP_NEW( EDT_TableCellData ); if( pData == 0 ){ // throw(); return pData; } pData->align = ED_ALIGN_DEFAULT; pData->valign = ED_ALIGN_ABSTOP; //ED_ALIGN_TOP; // was ED_ALIGN_DEFAULT; this is centerred - stupid! pData->iColSpan = 1; pData->iRowSpan = 1; pData->bHeader = FALSE; pData->bNoWrap = FALSE; pData->bWidthDefined = FALSE; pData->bWidthPercent = FALSE; pData->iWidth = 1; pData->iWidthPixels = 1; pData->iRow = 0; pData->iHeight = 1; pData->iHeightPixels = 1; pData->bHeightDefined = FALSE; pData->bHeightPercent = FALSE; pData->pColorBackground = 0; pData->pBackgroundImage = 0; pData->bBackgroundNoSave = FALSE; pData->pExtra = 0; pData->mask = -1; pData->iSelectionType = ED_HIT_NONE; pData->iSelectedCount = 0; pData->X = 0; pData->Y = 0; return pData; } void CEditTableCellElement::FreeData( EDT_TableCellData *pData ){ if( pData->pColorBackground ) XP_FREE( pData->pColorBackground ); if( pData->pBackgroundImage ) XP_FREE( pData->pBackgroundImage ); if( pData->pExtra ) XP_FREE( pData->pExtra ); XP_FREE( pData ); } void CEditTableCellElement::SetBackgroundColor( ED_Color iColor ){ m_backgroundColor = iColor; } ED_Color CEditTableCellElement::GetBackgroundColor(){ return m_backgroundColor; } void CEditTableCellElement::SetBackgroundImage( char* pImage ){ delete m_pBackgroundImage; m_pBackgroundImage = 0; if ( pImage ) { m_pBackgroundImage = XP_STRDUP(pImage); } } char* CEditTableCellElement::GetBackgroundImage(){ return m_pBackgroundImage; } LO_CellStruct* CEditTableCellElement::GetLoCell() { // Find the first leaf element in the cell since // it will have pointer to LO_Element CEditElement *pLeaf = FindNextElement(&CEditElement::FindLeafAll,0 ); if( pLeaf ) { // Only leaf edit elements have their pointers saved in LO_Elements LO_Element *pLoElement = pLeaf->Leaf()->GetLayoutElement(); if( pLoElement ) { // Find enclosing LO cell LO_CellStruct *pLoCell = lo_GetParentCell(GetEditBuffer()->m_pContext, pLoElement); // We shoul ALWAYS find a cell XP_ASSERT(pLoCell); return pLoCell; } } return NULL; } CEditTableCellElement* CEditTableCellElement::GetPreviousCellInTable(intn *pRowCounter) { // Simplest case is previous cell in the row CEditElement *pPreviousCell = GetPreviousSibling(); if( !pPreviousCell ) { // Get the last child cell of the previous row if( GetParent() && GetParent()->GetPreviousSibling() ) pPreviousCell = GetParent()->GetPreviousSibling()->GetLastChild(); #ifdef DEBUG if( pPreviousCell ) XP_ASSERT(pPreviousCell->IsTableCell()); #endif // Tell caller we wrapped to the previous row if( pRowCounter && pPreviousCell ) (*pRowCounter)--; } return (CEditTableCellElement*)pPreviousCell; } CEditTableCellElement* CEditTableCellElement::GetNextCellInTable(intn *pRowCounter) { // Simplest case is next cell in the row CEditElement *pNextCell = GetNextSibling(); if( !pNextCell ) { // Get the first child cell of the next row if( GetParent() && GetParent()->GetNextSibling() ) pNextCell = GetParent()->GetNextSibling()->GetChild(); // If the next child is not a cell, // we hit a table caption at the end of the table // (its the sibling after the last row in the table) // so there's no more cells if( pNextCell && !pNextCell->IsTableCell() ) return 0; // Tell caller we wrapped to the next row if( pRowCounter && pNextCell ) (*pRowCounter)++; } return (CEditTableCellElement*)pNextCell; } CEditTableCellElement* CEditTableCellElement::GetNextCellInLogicalRow() { CEditElement *pNextCell = GetNextSibling(); if( pNextCell && pNextCell->IsTableCell() ) return pNextCell->TableCell(); return NULL; } CEditTableCellElement* CEditTableCellElement::GetFirstCellInRow(int32 Y, XP_Bool bSpan) { // No Y supplied - just get it from the cell if( Y == -1 ) Y = GetY(); CEditTableElement *pTable = GetParentTable(); if( pTable ) return pTable->GetFirstCellInRow(Y, bSpan); return NULL; } CEditTableCellElement* CEditTableCellElement::GetFirstCellInColumn(int32 X, XP_Bool bSpan) { // No X supplied - just get it from the cell if( X == -1 ) X = GetX(); CEditTableElement *pTable = GetParentTable(); if( pTable ) return pTable->GetFirstCellInColumn(X, bSpan); return NULL; } CEditTableCellElement* CEditTableCellElement::GetNextCellInRow(CEditTableCellElement *pCell) { CEditTableElement *pTable = GetParentTable(); if( pTable ) return pTable->GetNextCellInRow(pCell); return NULL; } CEditTableCellElement* CEditTableCellElement::GetNextCellInColumn(CEditTableCellElement *pCell) { CEditTableElement *pTable = GetParentTable(); if( pTable ) return pTable->GetNextCellInColumn(pCell); return NULL; } XP_Bool CEditTableCellElement::AllCellsInColumnAreSelected() { CEditTableCellElement *pCell = GetFirstCellInColumn(); if( !pCell ) // Unlikely { XP_ASSERT(FALSE); return FALSE; } while( pCell ) { if( !pCell->IsSelected() ) return FALSE; pCell = GetNextCellInColumn(); } return TRUE; } XP_Bool CEditTableCellElement::AllCellsInRowAreSelected() { CEditTableCellElement *pCell = GetFirstCellInRow(); if( !pCell ) // Unlikely { XP_ASSERT(FALSE); return FALSE; } while( pCell ) { if( !pCell->IsSelected() ) return FALSE; pCell = GetNextCellInRow(); } return TRUE; } XP_Bool CEditTableCellElement::IsEmpty() { CEditElement *pElement = GetFirstMostChild(); // Should never happen, but if it does, // I guess we can consider it empty! if( !pElement ) return TRUE; // We are empty if no more sibling containers // and cell only has an empty string or // a single space in the string (used to force showing borders in browser) if( GetChild()->GetNextSibling() == 0 && pElement->IsText() && pElement->GetNextSibling() == 0 ) { char *pText = pElement->Text()->GetText(); // We should NEVER be missing text if( !pText ) return TRUE; int iLen = XP_STRLEN(pText); if( iLen == 0 || (iLen == 1 && pText[0] == ' ') ) return TRUE; } return FALSE; } void CEditTableCellElement::SplitCell() { CEditBuffer *pBuffer = GetEditBuffer(); if( pBuffer == NULL || (m_iColSpan == 0 && m_iRowSpan == 0) ) return; pBuffer->SetFillNewCellWithSpace(); CEditTableCellElement *pNewCell; int32 iNewRows = m_iRowSpan - 1; int32 iNewCells = m_iColSpan - 1; intn iNextRow = m_iRow + 1; CEditTableRowElement *pCurrentRow = (CEditTableRowElement*)GetParent(); XP_ASSERT(pCurrentRow && pCurrentRow->IsTableRow()); CEditTableRowElement *pRow = pCurrentRow; // Copy current cell's color and background image to all new cells EDT_TableCellData *pData = GetData(); EDT_TableCellData *pNewData = NewData(); XP_ASSERT(pData && pNewData); if( !pData || !pNewData ) return; pData->mask = CF_BACK_COLOR | CF_BACK_IMAGE; edt_CopyTableCellData(pNewData, pData); // Insert new cells in the current row if needed for( int i = 0; i < iNewCells; i++ ) { pNewCell = new CEditTableCellElement; pNewCell->InsertAfter(this); pNewCell->FinishedLoad(pBuffer); // Set background attributes pNewCell->SetData(pNewData); } // Insert new cells in following rows for( int iRow = 0; iRow < iNewRows; iRow++ ) { // Get the next existing row if( pRow ) pRow = (CEditTableRowElement*)pRow->GetNextSibling(); if( pRow ) { // Starting with first cell in row, find the cell // just after "this" cell's column CEditTableCellElement *pCell = (CEditTableCellElement*)pRow->GetChild(); while( pCell && pCell->GetX() <= m_X ) pCell = (CEditTableCellElement*)pCell->GetNextSibling(); // Insert new cells before the one we found for( int i = 0; i < m_iColSpan; i++ ) { pNewCell = new CEditTableCellElement; if( pCell ) pNewCell->InsertBefore(pCell); else // Row has no cells! (Table is messed up) pNewCell->InsertAsFirstChild(pRow); pNewCell->FinishedLoad(pBuffer); // Set background attributes pNewCell->SetData(pNewData); } } else { // If no row, table is messed up. // But we can fix it by simply inserting a new row CEditTableRowElement *pNewRow = new CEditTableRowElement(m_iColSpan); if( pNewRow == NULL ) break; pNewRow->InsertAfter(pCurrentRow); pCurrentRow = pNewRow; pRow->FinishedLoad(pBuffer); // Set background attributes in cells created with the row CEditTableCellElement *pNewRowCell = (CEditTableCellElement*)pRow->GetChild(); while( pNewRowCell ) { pNewRowCell->SetData(pNewData); pNewRowCell = (CEditTableCellElement*)pNewRowCell->GetNextSibling(); } } } // Now reset values in current cell pData->iColSpan = 1; pData->iRowSpan = 1; SetData(pData); EDT_FreeTableCellData(pData); EDT_FreeTableCellData(pNewData); pBuffer->ClearFillNewCellWithSpace(); } // Move all contents of supplied cell into this cell void CEditTableCellElement::MergeCells(CEditTableCellElement* pCell, int32& iNewRowSpan) { if( !pCell || pCell == this ) return; // Be sure cell is not selected // We do this in cell's destructor when we delete it below, // but its contents will be moved so the wrong LO_CellStruct will // be found if we don't do this now. GetEditBuffer()->SelectCell(FALSE, NULL, pCell); // Don't merge cells with only single, empty string as only element // Just delete the cell to be merged if( !pCell->IsEmpty() ) { CEditElement *pLastChild = GetChild(); if(!pLastChild) return; // First child container to move CEditElement *pMoveChild = pCell->GetChild(); if( IsEmpty() ) { // We are moving into an empty cell, // so delete the single container delete pLastChild; // Make the first element the first // container we are moving pMoveChild->SetParent( this ); SetChild(pMoveChild); // Setup for moving other containers pLastChild = pMoveChild; pMoveChild = pMoveChild->GetNextSibling(); } else { // Insert after last existing child of cell, // There is often only 1 container under each cell, // except if there are multiple paragraphs or // cell contents already merged while( pLastChild->GetNextSibling() ) pLastChild = pLastChild->GetNextSibling(); } while(pMoveChild) { // Move container by changing appropriate pointers pLastChild->SetNextSibling( pMoveChild ); pMoveChild->SetParent( this ); pLastChild = pMoveChild; pMoveChild = pMoveChild->GetNextSibling(); } // Clear pointer to children just moved pCell->SetChild(0); } CEditElement *pNext = pCell->GetNextSibling(); CEditElement *pPrev = pCell->GetPreviousSibling(); if( pCell->GetNextSibling() == NULL && pCell->GetPreviousSibling() == NULL ) { // pCell is the only cell in this row, so delete the row delete pCell->GetParent(); // Because we removed a row, the ROWSPAN is now 1 less iNewRowSpan--; } else { delete pCell; } } void CEditTableCellElement::DeleteContents(XP_Bool bMarkAsDeleted) { // Get the first conainer in the cell CEditElement *pChild = GetChild(); // Delete all other containers while( pChild ) { CEditElement *pNext = pChild->GetNextSibling(); pChild->Unlink(); delete pChild; pChild = pNext; } // Create a default paragraph container, // this will set cell as its parent CEditContainerElement *pContainer = CEditContainerElement::NewDefaultContainer(this, ED_ALIGN_DEFAULT); // Initialize the cell contents if( pContainer ) pContainer->FinishedLoad(GetEditBuffer()); // Mark this cell as deleted so CEditBuffer::DeleteSelectedCells() // can skip over this during multiple deletion passes if( bMarkAsDeleted ) m_bDeleted = TRUE; } // Insert as last child of supplied parent // but first save current parent and next pointers void CEditTableCellElement::SwitchLinkage(CEditTableRowElement *pParentRow) { // Save current location in tree m_pSaveParent = GetParent(); m_pSaveNext = GetNextSibling(); #ifdef DEBUG // This prevents XP_ASSERT when trying to InsertAsLastChild; SetParent(0); #endif // Insert into new row InsertAsLastChild((CEditElement*)pParentRow); } // Switches back to saved parent and next pointers void CEditTableCellElement::RestoreLinkage() { // Important! Can't call our CEditTableCellElement::Unlink() // because it will remove cell from selection list // and the main purpose for using this is to // restore selected cells temporarily moved // for copying to clipboard CEditElement::Unlink(); SetParent(m_pSaveParent); SetNextSibling(m_pSaveNext); CEditElement *pParent = GetParent(); } // Platform-specific end-of-line character(s) added to string, // returning in possibly-reallocated buffer that caller must free char *edt_AppendEndOfLine(char *pText) { //TODO: FIX THIS FOR MAC AND UNIX LINE-END CONVENTIONS if( pText ) return PR_sprintf_append( pText, "\r\n" ); return NULL; } char* CEditTableCellElement::GetText(XP_Bool bJoinParagraphs) { char *pCellText = NULL; CEditElement *pChild = GetChild(); //Prevents adding delimiter before first element XP_Bool bFirst = TRUE; while( pChild ) { if( pChild->IsContainer() ) { char *pText = pChild->Container()->GetText(); if( pText && *pText ) { if( !bFirst ) { if( bJoinParagraphs ) { // Add 2 spaces between paragraphs instead of CR/LF // so we can merge all contents in table cell pCellText = PR_sprintf_append( pCellText, " " ); } else { pCellText = edt_AppendEndOfLine( pCellText); } } pCellText = PR_sprintf_append(pCellText, pText); XP_FREE(pText); bFirst = FALSE; } } pChild = pChild->GetNextSibling(); } // We must follow the rule that every cell must have // at least 1 empty text element in it, // so return an empty string if nothing found if( !pCellText ) return XP_STRDUP(""); return pCellText; } // class CEditLayerElement CEditLayerElement::CEditLayerElement() : CEditElement(0, P_META) // P_LAYER, 0) { EDT_LayerData* pData = NewData(); // pData->iColumns = columns; SetData(pData); FreeData(pData); } CEditLayerElement::CEditLayerElement(CEditElement *pParent, PA_Tag *pTag, int16 /*csid*/) : CEditElement(pParent, P_META) // P_LAYER) { if( pTag ){ char *locked_buff; PA_LOCK(locked_buff, char *, pTag->data ); if( locked_buff ){ SetTagData( locked_buff ); } PA_UNLOCK(pTag->data); } } CEditLayerElement::CEditLayerElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer) : CEditElement(pStreamIn, pBuffer) { } void CEditLayerElement::FinishedLoad( CEditBuffer* pBuffer ){ EnsureSelectableSiblings(pBuffer); CEditElement::FinishedLoad(pBuffer); } void CEditLayerElement::SetData( EDT_LayerData *pData ){ char *pNew = 0; /* if ( pData->iColumns > 1){ pNew = PR_sprintf_append( pNew, "COLS=%d ", pData->iColumns ); } */ if( pData->pExtra ){ pNew = PR_sprintf_append( pNew, "%s ", pData->pExtra ); } if( pNew ){ pNew = PR_sprintf_append( pNew, ">" ); } SetTagData( pNew ); if ( pNew ) { free(pNew); } } EDT_LayerData* CEditLayerElement::GetData( ){ EDT_LayerData *pRet; PA_Tag* pTag = TagOpen(0); pRet = ParseParams( pTag, GetWinCSID() ); PA_FreeTag( pTag ); return pRet; } static char *LayerParams[] = { // PARAM_COLS, 0 }; EDT_LayerData* CEditLayerElement::ParseParams( PA_Tag *pTag, int16 csid ){ EDT_LayerData *pData = NewData(); // pData->iColumns = edt_FetchParamInt(pTag, PARAM_COLS, 1); pData->pExtra = edt_FetchParamExtras( pTag, LayerParams, csid ); return pData; } EDT_LayerData* CEditLayerElement::NewData(){ EDT_LayerData *pData = XP_NEW( EDT_LayerData ); if( pData == 0 ){ // throw(); return pData; } // pData->iColumns = 1; pData->pExtra = 0; return pData; } void CEditLayerElement::FreeData( EDT_LayerData *pData ){ if( pData->pExtra ) XP_FREE( pData->pExtra ); XP_FREE( pData ); } void CEditLayerElement::PrintOpen( CPrintState *pPrintState ){ TagType tSave = GetType(); SetType( P_LAYER ); CEditElement::PrintOpen( pPrintState ); SetType( tSave ); } void CEditLayerElement::PrintEnd( CPrintState *pPrintState ){ TagType tSave = GetType(); SetType( P_LAYER ); CEditElement::PrintEnd( pPrintState ); SetType( tSave ); } // CEditDivisionElement CEditDivisionElement::CEditDivisionElement() : CEditElement(0, P_DIVISION, 0) { EDT_DivisionData* pData = NewData(); SetData(pData); FreeData(pData); } CEditDivisionElement::CEditDivisionElement(CEditElement *pParent, PA_Tag *pTag, int16 /*csid*/) : CEditElement(pParent, P_DIVISION) { if( pTag ){ char *locked_buff; PA_LOCK(locked_buff, char *, pTag->data ); if( locked_buff ){ SetTagData( locked_buff ); } PA_UNLOCK(pTag->data); } } CEditDivisionElement::CEditDivisionElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer) : CEditElement(pStreamIn, pBuffer) { } XP_Bool CEditDivisionElement::IsDivision() { return TRUE; } CEditDivisionElement* CEditDivisionElement::Cast(CEditElement* pElement) { return pElement && pElement->IsDivision() ? (CEditDivisionElement*) pElement : 0; } EEditElementType CEditDivisionElement::GetElementType() { return eDivisionElement; } XP_Bool CEditDivisionElement::IsAcceptableChild(CEditElement& pChild){ return ! pChild.IsLeaf(); } void CEditDivisionElement::SetData( EDT_DivisionData *pData ){ char *pNew = 0; if( pData->align != ED_ALIGN_DEFAULT ){ pNew = PR_sprintf_append( pNew, "ALIGN=%s ", EDT_AlignString(pData->align) ); } if( pData->pExtra ){ pNew = PR_sprintf_append( pNew, "%s ", pData->pExtra ); } if( pNew ){ pNew = PR_sprintf_append( pNew, ">" ); } SetTagData( pNew ); if ( pNew ) { free(pNew); } } EDT_DivisionData* CEditDivisionElement::GetData( ){ EDT_DivisionData *pRet; PA_Tag* pTag = TagOpen(0); pRet = ParseParams( pTag, GetWinCSID() ); PA_FreeTag( pTag ); return pRet; } ED_Alignment CEditDivisionElement::GetAlignment(){ ED_Alignment result = ED_ALIGN_DEFAULT; EDT_DivisionData* pData = GetData(); if ( pData ) { result = pData->align; FreeData(pData); } return result; } void CEditDivisionElement::ClearAlignment(){ EDT_DivisionData* pData = GetData(); if ( pData ) { pData->align = ED_ALIGN_DEFAULT; SetData(pData); FreeData(pData); } } XP_Bool CEditDivisionElement::HasData(){ // Does this have data other than the ALIGN tag? XP_Bool hasData = FALSE; EDT_DivisionData* pData = GetData(); if ( pData ) { if ( pData->pExtra || pData->align != ED_ALIGN_DEFAULT ) { hasData = TRUE; } FreeData(pData); } return hasData; } static char *DivisionParams[] = { PARAM_ALIGN, 0 }; EDT_DivisionData* CEditDivisionElement::ParseParams( PA_Tag *pTag, int16 csid ){ EDT_DivisionData *pData = NewData(); pData->pExtra = edt_FetchParamExtras( pTag, DivisionParams, csid ); pData->align = edt_FetchParamAlignment( pTag, ED_ALIGN_DEFAULT, FALSE, csid ); return pData; } EDT_DivisionData* CEditDivisionElement::NewData(){ EDT_DivisionData *pData = XP_NEW( EDT_DivisionData ); if( pData == 0 ){ // throw(); return pData; } pData->align = ED_ALIGN_DEFAULT; pData->pExtra = 0; return pData; } void CEditDivisionElement::FreeData( EDT_DivisionData *pData ){ if ( pData ) { XP_FREEIF( pData->pExtra ); XP_FREE( pData ); } } void CEditDivisionElement::PrintOpen( CPrintState *pPrintState ){ // Don't print
tag if it is a no-op. if ( HasData() ) { CEditElement::PrintOpen(pPrintState); } } void CEditDivisionElement::PrintEnd( CPrintState *pPrintState ){ // Don't print
tag if it is a no-op. if ( HasData() ) { CEditElement::PrintEnd(pPrintState); } } //----------------------------------------------------------------------------- // CEditRootDocElement //----------------------------------------------------------------------------- void CEditRootDocElement::PrintOpen( CPrintState *pPrintState ){ m_pBuffer->PrintDocumentHead( pPrintState ); } void CEditRootDocElement::PrintEnd( CPrintState *pPrintState ){ m_pBuffer->PrintDocumentEnd( pPrintState ); } void CEditRootDocElement::FinishedLoad( CEditBuffer *pBuffer ){ CEditSubDocElement::FinishedLoad(pBuffer); if ( ! GetLastChild() || ! GetLastChild()->IsEndContainer() ) { CEditEndContainerElement* pEndContainer = new CEditEndContainerElement((CEditElement *)NULL); // Creating the element automaticly inserts it. (void) new CEditEndElement(pEndContainer); pEndContainer->InsertAsLastChild(this); } } #ifdef DEBUG void CEditRootDocElement::ValidateTree(){ CEditElement::ValidateTree(); XP_ASSERT(GetParent() == NULL); XP_ASSERT(GetChild() != NULL); XP_ASSERT(GetLastMostChild()->GetElementType() == eEndElement); } #endif //----------------------------------------------------------------------------- // CEditLeafElement //----------------------------------------------------------------------------- XP_Bool CEditLeafElement::PrevPosition(ElementOffset iOffset, CEditLeafElement*& pNew, ElementOffset& iNewOffset ){ XP_Bool bPreviousElement = FALSE; XP_Bool bMoved = TRUE; iNewOffset = iOffset - 1; pNew = this; if( iNewOffset == 0 ){ CEditElement *pPrev = PreviousLeafInContainer(); if( pPrev ){ if( pPrev->IsLeaf()){ bPreviousElement = TRUE; } } } // // we need to set the position to the end of the previous element. // if( iNewOffset < 0 || bPreviousElement ){ CEditLeafElement *pPrev = PreviousLeaf(); if( pPrev ){ if( pPrev->IsLeaf()){ pNew = pPrev; iNewOffset = pNew->Leaf()->GetLen(); } } else{ // no previous element, we are at the beginning of the buffer. iNewOffset = iOffset; bMoved = FALSE; } } return bMoved; } XP_Bool CEditLeafElement::NextPosition(ElementOffset iOffset, CEditLeafElement*& pNew, ElementOffset& iNewOffset ){ LO_Element* pRetText; CEditLeafElement *pNext; int iRetPos; XP_Bool retVal = TRUE; pNew = this; iNewOffset = iOffset + 1; // // if the new offset is obviously greater, move past it. If // Make sure the layout engine actually layed out the element. // if( iNewOffset > Leaf()->GetLen() || !Leaf()->GetLOElementAndOffset( iOffset, FALSE, pRetText, iRetPos )) { pNext = NextLeaf(); if( pNext ){ pNew = pNext; if( pNext->PreviousLeafInContainer() == 0 ){ iNewOffset = 0; } else { iNewOffset = 1; } } else { iNewOffset = iOffset; retVal = FALSE; } } return retVal; } void CEditLeafElement::DisconnectLayoutElements(LO_Element* /*pElement*/){ // The intention of this code was to make sure that live layout elements // didn't have back-pointers to dead edit elements. // // Unfortunately, if an edit element is cut out of the root, // and then deleted after a relayout, // the layout element will be stale. And that will cause // a Free Memory Read. This isn't usually a problem, except // on NT, where the rolling heap may have already freed the // heap that had the stale layout element. This happens // most frequently when large tables are deleted. // // This code doesn't seem to be needed for correct operation of // Composer. That's a good thing, because it can't be // made to work without a formal set of rules on the // lifetime of edit elements and layout elements. #if 0 // Clear back pointer out of the edit elements. LO_Element* e = pElement; while ( e && e->lo_any.edit_element == this ){ e->lo_any.edit_element = 0; while ( e && e->lo_any.edit_element == 0 ){ e = e->lo_any.next; } } #endif } void CEditLeafElement::SetLayoutElementHelper( intn desiredType, LO_Element** pElementHolder, intn iEditOffset, intn lo_type, LO_Element* pLoElement){ // XP_TRACE(("SetLayoutElementHelper this(0x%08x) iEditOffset(%d) pLoElement(0x%08x)", this, iEditOffset, pLoElement)); if( lo_type == desiredType ){ // iEditOffset is: // 0 for normal case, // -1 for end of document element. // > 0 for text items after a line wrap. // Don't disconnect layout elements because this is // called for each layout element of a wrapped text // edit element. Since DisconnectLayoutElements goes // ahead and zeros out all the "next" elements with // the same back pointer, we end up trashing later // elements if the elements are set in some order // other than first-to-last. This happens when just the 2nd line of a two-line // text element is being relaid out. if ( iEditOffset <= 0 ) { // Don't reset the old element, because functions like lo_BreakOldElement depend upon the // old element having a good value. *pElementHolder = pLoElement; } pLoElement->lo_any.edit_element = this; pLoElement->lo_any.edit_offset = iEditOffset; } else { // We get called back whenever linefeeds are generated. That's so that // breaks can find their linefeed. But it also means we are // called back more often than nescessary. // XP_TRACE((" ignoring inconsistent type.\n")); } } void CEditLeafElement::ResetLayoutElementHelper( LO_Element** pElementHolder, intn iEditOffset, LO_Element* pLoElement ){ // XP_TRACE(("ResetLayoutElementHelper this(0x%08x) iEditOffset(%d) pLoElement(0x%08x)", this, iEditOffset, pLoElement)); if ( iEditOffset <= 0 ) { if ( *pElementHolder == pLoElement ){ *pElementHolder = NULL; } else { XP_TRACE((" Woops -- we're currently pointing to 0x%08x\n", *pElementHolder)); } } } CEditElement* CEditLeafElement::Divide(int iOffset){ CEditElement* pNext; if( iOffset == 0 ){ return this; } pNext = LeafInContainerAfter(); if( iOffset >= GetLen() ){ if( pNext != 0 ){ return pNext; } else { CEditTextElement* pPrev; CEditTextElement* pNew; if( IsA( P_TEXT )) { pPrev = this->Text(); } else { pPrev = PreviousTextInContainer(); } if( pPrev ){ pNew = pPrev->CopyEmptyText(); } else { pNew = new CEditTextElement((CEditElement*)0,0); } pNew->InsertAfter(this); return pNew; } } // offset is in the middle somewhere Must be a TEXT. XP_ASSERT( IsA(P_TEXT) ); CEditTextElement* pNew = Text()->CopyEmptyText(); pNew->SetText( Text()->GetText()+iOffset ); Text()->GetText()[iOffset] = 0; /* update the text block with the new text */ CEditTextElement * pText = Text(); if ( pText->m_pTextBlock != NULL ){ lo_ChangeText ( pText->m_pTextBlock, pText->GetTextWithConvertedSpaces() ); } pNew->InsertAfter(this); return pNew; } CEditTextElement* CEditLeafElement::CopyEmptyText(CEditElement *pParent){ return new CEditTextElement(pParent,0); } CPersistentEditInsertPoint CEditLeafElement::GetPersistentInsertPoint(ElementOffset offset){ ElementIndex len = GetPersistentCount(); if ( offset > len ) { #ifdef DEBUG_EDIT_LAYOUT XP_ASSERT(FALSE); #endif offset = len; } return CPersistentEditInsertPoint(GetElementIndex() + offset, offset == 0); } XP_Bool CEditLeafElement::IsUnknownHTML(){ return FALSE; } XP_Bool CEditLeafElement::IsComment(){ return FALSE; } XP_Bool CEditLeafElement::IsComment(char* /*prefix*/){ return FALSE; } // // Default implementation // PA_Tag* CEditLeafElement::TagOpen( int /* iEditOffset */ ){ PA_Tag *pTag = XP_NEW( PA_Tag ); XP_BZERO( pTag, sizeof( PA_Tag ) ); if( GetTagData() ){ SetTagData( pTag, GetTagData() ); } else { SetTagData( pTag, ">" ); } return pTag; } ElementIndex CEditLeafElement::GetPersistentCount() { return GetLen(); } void CEditLeafElement::GetAll(CEditSelection& selection) { selection.m_start.m_pElement = this; selection.m_start.m_iPos = 0; selection.m_end.m_pElement = this; selection.m_end.m_iPos = Leaf()->GetLen(); selection.m_bFromStart = FALSE; } XP_Bool CEditLeafElement::IsAcceptableChild(CEditElement& /*pChild*/ ) {return FALSE;} XP_Bool CEditLeafElement::IsContainerContainer() { return FALSE; } /* Optimized version for leaves. */ XP_Bool CEditLeafElement::IsFirstInContainer(){ CEditContainerElement* pContainer = FindContainer(); if ( ! pContainer ) { return TRUE; } CEditElement* pOther = pContainer->GetChild(); return pOther == this; } #ifdef DEBUG void CEditLeafElement::ValidateTree(){ CEditElement::ValidateTree(); CEditElement *pChild = GetChild(); XP_ASSERT(!pChild); // Must have no children. // Parent must exist and be a container CEditElement *pParent = GetParent(); XP_ASSERT(pParent); XP_ASSERT(pParent->IsContainer()); } #endif XP_Bool CEditLeafElement::IsMisspelled() { return (IsText() && Text()->m_tf & TF_SPELL); } void CEditLeafElement::StreamToPositionalText( IStreamOut *pOut, XP_Bool bEnd ){ if( !bEnd ){ XP_ASSERT( GetLen() == 1 ); pOut->Write(" ",1); } } //----------------------------------------------------------------------------- // CEditContainerElement //----------------------------------------------------------------------------- // // Static constructor to create default element // CEditContainerElement* CEditContainerElement::NewDefaultContainer( CEditElement *pParent, ED_Alignment align ){ PA_Tag *pTag = XP_NEW( PA_Tag ); XP_BZERO( pTag, sizeof( PA_Tag ) ); #ifdef EDT_DDT pTag->type = P_NSDT; #else pTag->type = P_DESC_TITLE; #endif // CSID doesn't matter because there's no data in the pTag, so there's no i18n issues. CEditContainerElement *pRet = new CEditContainerElement( pParent, pTag, 0, align ); PA_FreeTag( pTag ); return pRet; } CEditContainerElement::CEditContainerElement(CEditElement *pParent, PA_Tag *pTag, int16 csid, ED_Alignment defaultAlign ): CEditElement(pParent, pTag ? pTag->type : P_PARAGRAPH), m_defaultAlign( defaultAlign ), m_align( defaultAlign ), m_hackPreformat(0), m_bHasEndTag(FALSE) { if( pTag ){ EDT_ContainerData *pData = ParseParams( pTag, csid ); SetData( pData ); FreeData( pData ); } } CEditContainerElement::CEditContainerElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer): CEditElement( pStreamIn, pBuffer ) { m_defaultAlign = (ED_Alignment) pStreamIn->ReadInt(); m_align = (ED_Alignment) pStreamIn->ReadInt(); m_bHasEndTag = FALSE; } void CEditContainerElement::StreamOut( IStreamOut *pOut ){ CEditElement::StreamOut( pOut ); pOut->WriteInt( m_defaultAlign ); pOut->WriteInt( m_align ); } void CEditContainerElement::StreamInChildren(IStreamIn* pIn, CEditBuffer* pBuffer){ CEditElement::StreamInChildren(pIn, pBuffer); if ( GetChild() == NULL ){ // I need a dummy child. // g++ gets issues a spurious warning about the value not being used. // Creating it automaticly inserts it (void) new CEditTextElement(this, 0); } } ElementIndex CEditContainerElement::GetPersistentCount(){ // One extra element: the end of the last item in the container. return CEditElement::GetPersistentCount() + 1; } void CEditContainerElement::FinishedLoad( CEditBuffer *pBuffer ) { if( !pBuffer ) pBuffer = GetEditBuffer(); if( !pBuffer ) { XP_ASSERT(FALSE); CEditElement::FinishedLoad(pBuffer); return; } XP_Bool bFillNewCellWithSpace = FALSE; if ( GetType() != P_PREFORMAT ) { // Don't allow more than one space before non-text elements CEditElement* pNext; CEditElement* pParent = GetParent(); CEditElement *pChild = GetChild(); // Detect the case of a single container with a single // text element with just a space in a table cell // This is OK and is needed to show cell border in browser XP_Bool bIsTableCellWithOneSpace = FALSE; if( pParent && pParent->IsTableCell() && GetNextSibling() == NULL ) { if( !pChild ) // If creating a new cell and preference is set, // the empty cell should have a space, // so delete this and a new one will be created below //bFillNewCellWithSpace = pBuffer ? pBuffer->FillNewCellWithSpace() : FALSE; bFillNewCellWithSpace = pBuffer->FillNewCellWithSpace(); if( pChild && pChild->IsText() && (pChild->GetNextSibling() == NULL || pChild->GetNextSibling()->GetType() == P_UNKNOWN) ) { // We have a single container in a table cell with a single text child CEditTextElement* pTextChild = pChild->Text(); char* pText = pTextChild->GetText(); bFillNewCellWithSpace = pBuffer->FillNewCellWithSpace(); if( pTextChild->GetLen() == 0 && bFillNewCellWithSpace ) { delete pChild; } else if( pTextChild->GetLen() == 1 && pText[0] == ' ') { // We have a single space, so don't do special trimming below bIsTableCellWithOneSpace = TRUE; } } CEditBuffer *t_buffer; t_buffer = GetEditBuffer(); if( ! bIsTableCellWithOneSpace && (t_buffer && !t_buffer->m_bNoRelayout)) { for ( CEditElement* pChild = GetChild(); pChild; pChild = pNext ) { pNext = pChild->GetNextSibling(); if ( pChild->IsText() && ! (pNext && pNext->IsText()) ) { CEditTextElement* pTextChild = pChild->Text(); char* pText = pTextChild->GetText(); int size = pTextChild->GetLen(); XP_Bool trimming = FALSE; //do not allow 1 byte of space to be a child if (size == 1 && GetType() == P_NSDT && pText[0] == ' ') { trimming = TRUE; size--; } while ( size >0 && pText[size-1] == '\n' || (size > 1 && pText[size-1] == ' ' && pText[size-2] == ' ') ) { // More than one character of white space at the end of // a text element will get laid out as one character. trimming = TRUE; size--; } if ( trimming ) { if (size <= 0 ) { delete pChild; } else { char* pCopy = XP_STRDUP(pText); pCopy[size] = '\0'; pTextChild->SetText(pCopy); XP_FREE(pCopy); } } } } } } } if( GetChild() == NULL ) { // I need a dummy child -- might be a space for new cell CEditTextElement* pChild = new CEditTextElement(this, bFillNewCellWithSpace ? " " : 0); // Creating it automaticly inserts it pChild->FinishedLoad(pBuffer); } // // if the last element in a paragraph is a
, it is ignored by the browser. // So we should trim it. // we should do this only once!!! CEditElement *pChild; CEditElement* pPrev; if( (pChild = GetLastChild())!= 0 && pChild->IsBreak()) { pPrev = pChild->GetPreviousSibling(); if (! (pPrev && pPrev->IsBreak()) ) delete pChild; } CEditElement::FinishedLoad(pBuffer); } XP_Bool CEditContainerElement::ForcesDoubleSpacedNextLine() { return BitSet( edt_setContainerHasLineAfter, GetType() ); } //helper function //default == left! PRIVATE XP_Bool CompareAlignments(ED_Alignment x, ED_Alignment y) { if (x == y) return TRUE; if ( ((x == ED_ALIGN_ABSCENTER) || (x == ED_ALIGN_CENTER)) && ((y == ED_ALIGN_ABSCENTER) || (y == ED_ALIGN_CENTER)) ) return TRUE; if ( ((x == ED_ALIGN_DEFAULT) || (x == ED_ALIGN_LEFT)) && ((y == ED_ALIGN_DEFAULT) || (y == ED_ALIGN_LEFT)) ) return TRUE; if ( ((x == ED_ALIGN_ABSBOTTOM) || (x == ED_ALIGN_BOTTOM)) && ((y == ED_ALIGN_ABSBOTTOM) || (y == ED_ALIGN_BOTTOM)) ) return TRUE; if ( ((x == ED_ALIGN_ABSTOP) || (x == ED_ALIGN_TOP)) && ((y == ED_ALIGN_ABSTOP) || (y == ED_ALIGN_TOP)) ) return TRUE; return FALSE; } void CEditContainerElement::AdjustContainers( CEditBuffer *pBuffer ){ if( GetType() == P_PARAGRAPH || (pBuffer->IsComposeWindow() && GetType() == P_PREFORMAT && m_hackPreformat ) ){ XP_Bool bInsertNewContainer = FALSE; XP_Bool bIsParagraph = GetType() == P_PARAGRAPH; // // Convert to internal paragraph type // SetType( P_NSDT ); CEditElement *pPreviousSibling = (CEditElement*) GetPreviousSibling(); CEditContainerElement *pPrev = 0; CEditContainerElement *pNext = (CEditContainerElement*) GetNextSibling(); // // Just some paranoia // if ( pPreviousSibling && pPreviousSibling->IsContainer() ) pPrev = (CEditContainerElement*) pPreviousSibling; if (!pNext) bInsertNewContainer = TRUE; else if (pNext->IsContainer()) { // // If the alignments of this container and the next one // don't match, add a blank container after this container. // // - Or - // // If this paragraph has an end tag, and the next container // is an NSDT, add a blank container after this paragraph. // // This will allow us to handle cases like: // //


//

text //

// if (!CompareAlignments(GetAlignment(), pNext->GetAlignment()) || (bIsParagraph && m_bHasEndTag && pNext->GetType() == P_NSDT)) bInsertNewContainer = TRUE; } else if (bIsParagraph && ((!IsListContainer(pNext->GetType()) && !pNext->IsTable()) || (m_bHasEndTag && pNext->IsTable()))) { // // Dont' add a blank container between a paragraph and // a list ... layout does that for us for free. // // If the paragraph is followed by a table, add a blank // container only if the paragraph has a

tag. // bInsertNewContainer = TRUE; } if ( bInsertNewContainer ){ CEditContainerElement *pNew; pNew = CEditContainerElement::NewDefaultContainer( 0, GetAlignment() ); // g++ thinks the following value is not used, but it's mistaken. // The constructor auto-inserts the element into the tree. (void) new CEditBreakElement(pNew, 0); pNew->InsertAfter( this ); } // // If there is a previous container, append an empty single spaced // paragraph before this one. also append an empty single spaced paragraph if it was a table. // if ((pPreviousSibling && pPreviousSibling->GetType() == P_TABLE) || (pPreviousSibling && pPreviousSibling->GetType() == P_DIVISION) || ( pPrev && !pPrev->ForcesDoubleSpacedNextLine()) ) { CEditContainerElement *pNew; pNew = CEditContainerElement::NewDefaultContainer( 0, GetAlignment() ); // g++ thinks the following value is not used, but it's mistaken. // The constructor auto-inserts the element into the tree. (void) new CEditTextElement(pNew, 0); pNew->InsertBefore( this ); } } if( BitSet( edt_setContainerBreakConvert, GetType() )) { // Find any breaks in this paragraph and convert them into paragraphs CEditLeafElement *pChild = (CEditLeafElement*) GetChild(); CEditLeafElement *pPrev = 0; while( pChild ){ CEditLeafElement *pNext = pChild->LeafInContainerAfter(); if( pChild->IsBreak() #ifdef USE_SCRIPT && !(pPrev && pPrev->IsText() && (pPrev->Text()->m_tf & (TF_SCRIPT|TF_STYLE|TF_SERVER)) != 0 ) && !(pNext && pNext->IsText() && (pNext->Text()->m_tf & (TF_SCRIPT|TF_STYLE|TF_SERVER)) != 0 ) #endif ){ // // If the first thing in the paragraph is a break, // make the paragraph contain empty text // if( pChild->PreviousLeafInContainer() == 0 ){ CEditTextElement *pNewText = new CEditTextElement((CEditElement*)0, 0); pNewText->InsertBefore( pChild ); } // Break the chain of children So unlink doesn't try and // merge the chain pChild->SetNextSibling(0); // Remove the break. pChild->Unlink(); delete pChild; pChild = 0; // reparent the children. if( pNext ){ // // Create a new container // CEditContainerElement *pNew; pNew = CEditContainerElement::NewDefaultContainer( 0, GetAlignment() ); pNew->InsertAfter(this); pNew->SetChild(pNext); while( pNext ){ pNext->SetParent( pNew ); pNext = (CEditLeafElement*)pNext->GetNextSibling(); } } } pPrev = pChild; pChild = pNext; } } CEditElement::AdjustContainers(pBuffer); } PA_Tag* CEditContainerElement::TagOpen( int iEditOffset ) { PA_Tag *pRet = 0; PA_Tag* pTag; // create the DIV tag if we need to. if( m_align == ED_ALIGN_LEFT || m_align == ED_ALIGN_ABSCENTER || m_align == ED_ALIGN_RIGHT ) { pTag = XP_NEW( PA_Tag ); XP_BZERO( pTag, sizeof( PA_Tag ) ); // Explicitely create LEFT
element if not ED_ALIGN_DEFAULT if( m_align== ED_ALIGN_LEFT ) { SetTagData( pTag, "ALIGN=left>"); pTag->type = P_DIVISION; } else if( m_align== ED_ALIGN_RIGHT ) { SetTagData( pTag, "ALIGN=right>"); pTag->type = P_DIVISION; } else { SetTagData( pTag, ">"); pTag->type = P_CENTER; } pRet = pTag; } // create the actual paragraph tag if( GetTagData() ) { pTag = XP_NEW( PA_Tag ); XP_BZERO( pTag, sizeof( PA_Tag ) ); SetTagData( pTag, GetTagData() ); } else { pTag = CEditElement::TagOpen( iEditOffset ); } // link the tags together. if( pRet == 0 ) pRet = pTag; else pRet->next = pTag; return pRet; } PA_Tag* CEditContainerElement::TagEnd( ) { PA_Tag *pRet = CEditElement::TagEnd(); if( m_align == ED_ALIGN_LEFT || m_align == ED_ALIGN_ABSCENTER || m_align == ED_ALIGN_RIGHT ) { PA_Tag* pTag = XP_NEW( PA_Tag ); XP_BZERO( pTag, sizeof( PA_Tag ) ); pTag->is_end = TRUE; // Explicitely create LEFT
element if not ED_ALIGN_DEFAULT if( m_align== ED_ALIGN_LEFT ) { pTag->type = P_DIVISION; } else if( m_align == ED_ALIGN_RIGHT ) { pTag->type = P_DIVISION; } else { pTag->type = P_CENTER; } if( pRet == 0 ) pRet = pTag; else pRet->next = pTag; } return pRet; } EEditElementType CEditContainerElement::GetElementType() { return eContainerElement; } XP_Bool CEditContainerElement::IsPlainFirstContainer(){ if ( (GetType() == P_PARAGRAPH || GetType() == P_NSDT) && ! HasExtraData() && IsFirstContainer() ) { ED_Alignment alignment = GetAlignment(); ED_Alignment defaultAlignment = GetDefaultAlignment(); if ( alignment == ED_ALIGN_DEFAULT || alignment == defaultAlignment || ( alignment == ED_ALIGN_LEFT && defaultAlignment == ED_ALIGN_DEFAULT )) { return TRUE; } } return FALSE; } XP_Bool CEditContainerElement::IsFirstContainer(){ CEditSubDocElement* pSubDoc = GetSubDoc(); return ! pSubDoc || pSubDoc->GetChild() == this; } XP_Bool CEditContainerElement::SupportsAlign(){ return BitSet( edt_setContainerSupportsAlign, GetType() ); } XP_Bool CEditContainerElement::IsImplicitBreak() { XP_ASSERT( GetType() == P_NSDT ); CEditElement *pPrev; return ( (pPrev = GetPreviousSibling()) == 0 || !pPrev->IsContainer() || pPrev->Container()->ForcesDoubleSpacedNextLine() #if 0 || ((pPrev->Container()->GetAlignment() == ED_ALIGN_RIGHT) && (GetAlignment() != ED_ALIGN_RIGHT) ) || ((pPrev->Container()->GetAlignment() != ED_ALIGN_RIGHT) && (GetAlignment() == ED_ALIGN_RIGHT) ) #endif ); } // 0..2, where 0 = not in pseudo paragraph. // 1 = first container of pseudo paragraph. // 2 = second container of pseudo paragraph. // 3 = after list,

does NOT cause a blank line to be drawn above paragraph. we need


int16 CEditContainerElement::GetPseudoParagraphState(){ if( GetType() != P_NSDT ){ return 0; } CEditElement *pPrev = GetPreviousSibling(); CEditElement *pPrevPrev; if (pPrev) pPrevPrev = pPrev->GetPreviousSibling(); else pPrevPrev=NULL; CEditElement *pNext = GetNextSibling(); if ( !pPrev || (!pPrev->IsContainer()) && !IsEmpty() ) { return 0; } if (!pPrev->IsContainer()) return 1; CEditContainerElement* pPrevContainer = pPrev->Container(); CEditContainerElement* pPrevPrevContainer=NULL; if ((pPrevPrev) &&(pPrevPrev->IsContainer())) pPrevPrevContainer = pPrevPrev->Container(); if ( IsEmpty() && pPrevContainer->GetType() == P_NSDT && pPrevContainer->IsEmpty() && (!pPrevPrevContainer || pPrevPrevContainer->GetType() != P_NSDT) && pNext && pNext->IsContainer() && pNext->Container()->GetType() == P_NSDT && pNext->Container()->IsEmpty() ) { return 5; //special for 3 spaces. we must settle up the
tags now.

later wont cut it. } if ( IsEmpty() && pPrevContainer->GetType() == P_NSDT && pPrevContainer->IsEmpty() && (!pPrevPrevContainer || pPrevPrevContainer->GetType() != P_NSDT) ) { return 4; } if ( !IsEmpty() && pPrevContainer->GetType() == P_NSDT && pPrevContainer->IsEmpty() && (!pPrevPrev || pPrevPrev->GetType()!= P_TABLE)//tables do not enforce a break!

is sufficient for 1 break! && (!pPrevPrevContainer || pPrevPrevContainer->GetType() != P_NSDT) ) { return 3; } if ( ! IsEmpty() && ((pPrevContainer->GetType() == P_NSDT && pPrevContainer->IsEmpty())|| pPrevPrev && pPrevPrev->GetType() == P_TABLE) ) { return 2; } // Check for state 1 if ( IsEmpty() && pNext && pNext->IsContainer() && pNext->Container()->GetType() == P_NSDT && ! pNext->Container()->IsEmpty() ) { return 1; } // NOTE: there needs to be a BR tag when the previous non empty container is the same alignment as this one! //the helper function is mainly to make sure default and left are equivalent pPrevContainer= GetPreviousNonEmptyContainer(); //check nonempty! if ( !IsEmpty() && pPrevContainer && !CompareAlignments(pPrevContainer->GetAlignment(),GetAlignment()) ) return 1; return 0; } //NOTES: /* It is not necessary to put a BR tag to break after

or
. there IS an implicit break of sorts at that point */ void CEditContainerElement::PrintOpen( CPrintState *pPrintState ){ if ( ShouldSkipTags() ) return; XP_Bool bHasExtraData = HasExtraData(); #ifdef EDT_DDT if( GetType() == P_NSDT ){ int16 ppState = GetPseudoParagraphState(); CEditContainerElement* pPrevContainer=GetPreviousNonEmptyContainer(); if (pPrevContainer && pPrevContainer->GetType()!=P_NSDT) pPrevContainer=NULL;//all bets are off // We must always set alignment explicitly for text in TableCaptions // For this to work, we must change use ED_ALIGN_DEFAULT instead of ED_ALIGN_LEFT // when parsing regular paragraphs so we don't clutter HTML with extra
alignment tags if (( GetAlignment() == ED_ALIGN_LEFT && ! IsEmpty()) && ( !pPrevContainer || (pPrevContainer->GetAlignment() != ED_ALIGN_LEFT) )) { pPrintState->m_pOut->Printf( "\n"); pPrintState->m_iCharPos = pPrintState->m_pOut->Printf( "
"); } else if (( GetAlignment() == ED_ALIGN_RIGHT && ! IsEmpty()) && ( !pPrevContainer || (pPrevContainer->GetAlignment() != ED_ALIGN_RIGHT ) )) { pPrintState->m_pOut->Printf( "\n"); pPrintState->m_iCharPos = pPrintState->m_pOut->Printf( "
"); } else if (( GetAlignment() == ED_ALIGN_ABSCENTER && !IsEmpty()) && ( !pPrevContainer || (pPrevContainer->GetAlignment() != ED_ALIGN_ABSCENTER ) )) { pPrintState->m_pOut->Printf( "\n"); pPrintState->m_iCharPos = pPrintState->m_pOut->Printf( "
"); } if( !IsImplicitBreak() && ! bHasExtraData) { switch( ppState ){ default: case 0: pPrintState->m_pOut->Printf( "\n"); pPrintState->m_iCharPos = pPrintState->m_pOut->Printf("
"); break; case 1: // Do nothing. This is an empty paragraph. break; case 2: pPrintState->m_pOut->Printf( "\n"); pPrintState->m_iCharPos = pPrintState->m_pOut->Printf( "

"); break; case 3: pPrintState->m_pOut->Printf( "\n"); //used for 1 space after listitem. pPrintState->m_iCharPos = pPrintState->m_pOut->Printf( "


"); break; case 4: pPrintState->m_pOut->Printf( "\n"); //used for 2 open spaces after listitems for example pPrintState->m_iCharPos = pPrintState->m_pOut->Printf( "

 "); //only gives us one. //if

is comming, we will get 2 line spacing. if we wanted more, //we should have settled up with a 5 on return from pseudo break; case 5: pPrintState->m_pOut->Printf( "\n"); //used for 3 open spaces after listitems for example pPrintState->m_pOut->Printf( "

 "); //gives 2 spaces after list type item pPrintState->m_pOut->Printf( "\n"); pPrintState->m_iCharPos = pPrintState->m_pOut->Printf( "
 "); break; } } if ( bHasExtraData ) { EDT_ContainerData* pData = GetData(); if ( pData && pData->pExtra ) { pPrintState->m_pOut->Printf( "\n"); if (ppState == 2) pPrintState->m_pOut->Printf( "\n"); char* pTagName = ppState == 2 ? "P" : "DIV"; pPrintState->m_iCharPos = pPrintState->m_pOut->Printf( "<%s %s>", pTagName, pData->pExtra); } } return; } #endif pPrintState->m_iCharPos = 0; pPrintState->m_pOut->Printf("\n"); PA_Tag *pTag = TagOpen(0); while( pTag ){ char *pData = 0; if( pTag->data ){ PA_LOCK( pData, char*, pTag->data ); } if( pData && *pData != '>' ) { pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "<%s %s", EDT_TagString(pTag->type), pData ); } else { pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "<%s>", EDT_TagString(pTag->type)); } if( pTag->data ){ PA_UNLOCK( pTag->data ); } PA_Tag* pDelTag = pTag; pTag = pTag->next; PA_FreeTag(pDelTag); if( GetType() != P_PREFORMAT ){ pPrintState->m_pOut->Printf( "\n"); pPrintState->m_iCharPos = 0; } } } XP_Bool CEditContainerElement::ShouldSkipTags(){ XP_Bool parentIsTableCell = GetParent() && GetParent()->IsTableCell(); return parentIsTableCell && IsPlainFirstContainer(); } XP_Bool CEditContainerElement::HasExtraData(){ // Does this have data other than the ALIGN tag? XP_Bool hasData = FALSE; EDT_ContainerData* pData = GetData(); if ( pData ) { if ( pData->pExtra ) { hasData = TRUE; } FreeData(pData); } return hasData; } void CEditContainerElement::PrintEnd( CPrintState *pPrintState ){ if ( ShouldSkipTags() ) return; #ifdef EDT_DDT if( GetType() == P_NSDT ){ int16 ppState = GetPseudoParagraphState(); XP_Bool bNeedReturn = FALSE; CEditContainerElement* pNextContainer=GetNextNonEmptyContainer(); if ( HasExtraData() ){ if ( ppState != 2 ){ pPrintState->m_pOut->Printf( "

"); bNeedReturn = TRUE; } } // Keep empty paragraphs from being eaten if ( IsEmpty() && ppState == 0) { char* space = " "; pPrintState->m_pOut->Printf( space ); pPrintState->m_iCharPos += XP_STRLEN(space); } ED_Alignment align = GetAlignment(); if( !IsEmpty() && (align == ED_ALIGN_RIGHT || align == ED_ALIGN_LEFT) && (!pNextContainer || pNextContainer->GetType() != P_NSDT || !CompareAlignments(pNextContainer->GetAlignment(), align)) ) { pPrintState->m_pOut->Printf( "
"); bNeedReturn = TRUE; } else if(( GetAlignment() == ED_ALIGN_ABSCENTER && !IsEmpty() ) && (!pNextContainer || pNextContainer->GetType() != P_NSDT || !CompareAlignments(pNextContainer->GetAlignment(), align)) ) { pPrintState->m_pOut->Printf( "
"); bNeedReturn = TRUE; } if ( bNeedReturn ) { pPrintState->m_iCharPos = 0; pPrintState->m_pOut->Printf( "\n"); } return; } #endif PA_Tag *pTag = TagEnd(); while( pTag ){ char *pData = 0; if( pTag->data ){ PA_LOCK( pData, char*, pTag->data ); } pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "", EDT_TagString( pTag->type ) ); if( pTag->data ){ PA_UNLOCK( pTag->data ); } PA_Tag* pDelTag = pTag; pTag = pTag->next; PA_FreeTag(pDelTag); } pPrintState->m_iCharPos = 0; pPrintState->m_pOut->Printf( "\n"); } CEditElement* CEditContainerElement::Clone( CEditElement* pParent ){ PA_Tag *pTag = TagOpen(0); // LTNOTE: we get division tags at the beginning of paragraphs // for their alignment. Ignore these tags, we get it directly from the // container itself. // Its a total hack. // if( pTag->type == P_DIVISION || pTag->type == P_CENTER ){ PA_Tag *pDelTag = pTag; pTag = pTag->next; PA_FreeTag( pDelTag ); } CEditElement *pRet = new CEditContainerElement(pParent, pTag, GetWinCSID(), m_align ); PA_FreeTag( pTag ); return pRet; } // Property Getting and Setting Stuff. EDT_ContainerData* CEditContainerElement::NewData(){ EDT_ContainerData *pData = XP_NEW( EDT_ContainerData ); pData->align = ED_ALIGN_LEFT; pData->pExtra = 0; return pData; } void CEditContainerElement::FreeData( EDT_ContainerData *pData ){ if ( pData ) { XP_FREEIF(pData->pExtra); XP_FREE( pData ); } } void CEditContainerElement::SetData( EDT_ContainerData *pData ){ char *pNew = 0; m_align = pData->align; if( m_align == ED_ALIGN_CENTER ) m_align = ED_ALIGN_ABSCENTER; // // Never generate ALIGN= stuff anymore. // Use the Div tag for LEFT and RIGHT, and the CENTER tag // char* pExtra = ""; if ( pData && pData->pExtra ) { pExtra = pData->pExtra; } pNew = PR_sprintf_append( pNew, "%s>", pExtra ); SetTagData( pNew ); free(pNew); } EDT_ContainerData* CEditContainerElement::GetData( ){ EDT_ContainerData *pRet; PA_Tag* pTag = TagOpen(0); pRet = ParseParams( pTag, GetWinCSID() ); EDT_DeleteTagChain( pTag ); return pRet; } static char* containerParams[] = { PARAM_ALIGN, "nscisaw", 0 }; EDT_ContainerData* CEditContainerElement::ParseParams( PA_Tag *pTag, int16 csid ){ EDT_ContainerData *pData = NewData(); ED_Alignment align; m_hackPreformat = edt_FetchParamBoolExist(pTag, "nscisaw", csid); align = edt_FetchParamAlignment( pTag, m_defaultAlign, FALSE, csid ); if( align == ED_ALIGN_CENTER ) align = ED_ALIGN_ABSCENTER; if( align == ED_ALIGN_DEFAULT || align == ED_ALIGN_RIGHT || align == ED_ALIGN_LEFT || align == ED_ALIGN_ABSCENTER){ pData->align = align; } pData->pExtra = edt_FetchParamExtras( pTag, containerParams, csid ); return pData; } void CEditContainerElement::SetAlignment( ED_Alignment eAlign ){ EDT_ContainerData *pData = GetData(); pData->align = eAlign; SetData( pData ); FreeData( pData ); } void CEditContainerElement::CopyAttributes(CEditContainerElement *pFrom ){ SetType( pFrom->GetType() ); SetAlignment( pFrom->GetAlignment() ); } XP_Bool CEditContainerElement::IsEmpty(){ CEditElement *pChild = GetChild(); return ( pChild == 0 || ( pChild->IsA( P_TEXT ) && pChild->Text()->GetLen() == 0 && pChild->LeafInContainerAfter() == 0 ) ); } #ifdef DEBUG void CEditContainerElement::ValidateTree(){ CEditElement::ValidateTree(); CEditElement* pChild = GetChild(); XP_ASSERT(pChild); // Must have at least one child. } #endif // // Sets the alignment if the container is empty (or almost empty). Used only // during the parse phase to handle things like

// void CEditContainerElement::AlignIfEmpty( ED_Alignment eAlign ){ CEditElement *pChild = GetChild(); while( pChild ){ if( pChild->IsA( P_TEXT ) && pChild->Leaf()->GetLen() != 0 ){ return; } pChild = pChild->GetNextSibling(); } SetAlignment( eAlign ); } void CEditContainerElement::StreamToPositionalText( IStreamOut *pOut, XP_Bool bEnd ){ if( bEnd ){ pOut->Write(" ",1); } } char* CEditContainerElement::GetText() { char *pText = NULL; CEditElement *pChild = GetChild(); while( pChild ) { if( pChild->IsText() && pChild->Leaf()->GetLen() != 0 ) { pText = PR_sprintf_append( pText, pChild->Text()->GetText() ); } pChild = pChild->GetNextSibling(); } return pText; } //----------------------------------------------------------------------------- // CEditListElement //----------------------------------------------------------------------------- CEditListElement::CEditListElement( CEditElement *pParent, PA_Tag *pTag, int16 /*csid*/ ): CEditElement( pParent, pTag->type ){ if( pTag ){ char *locked_buff; PA_LOCK(locked_buff, char *, pTag->data ); SetTagData( locked_buff ); PA_UNLOCK(pTag->data); } m_pBaseURL = NULL; } CEditListElement::CEditListElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer): CEditElement( pStreamIn, pBuffer ) { m_pBaseURL = NULL; } CEditListElement::~CEditListElement() { XP_FREEIF(m_pBaseURL); } PA_Tag* CEditListElement::TagOpen( int /* iEditOffset */ ){ PA_Tag *pTag = XP_NEW( PA_Tag ); XP_BZERO( pTag, sizeof( PA_Tag ) ); SetTagData( pTag, GetTagData() ? GetTagData() : ">" ); return pTag; } CEditElement* CEditListElement::Clone( CEditElement *pParent ){ PA_Tag* pTag = TagOpen(0); CEditElement* pNew = new CEditListElement( pParent, pTag, GetWinCSID() ); PA_FreeTag( pTag ); return pNew; } XP_Bool CEditListElement::IsCompatableList(CEditElement *pElement) { if( pElement && pElement->IsList() ) { TagType t = pElement->GetType(); if( t == m_tagType || ((t == P_UNUM_LIST && m_tagType == P_NUM_LIST) || (m_tagType == P_UNUM_LIST && t == P_NUM_LIST)) ) return TRUE; } return FALSE; } // Property Getting and Setting Stuff. EDT_ListData* CEditListElement::NewData(){ EDT_ListData *pData = XP_NEW( EDT_ListData ); if( pData == 0 ){ // throw(); return pData; } pData->iTagType = P_UNUM_LIST; pData->bCompact = FALSE; pData->eType = ED_LIST_TYPE_DEFAULT; pData->iStart = 1; pData->pExtra = 0; pData->pBaseURL = NULL; return pData; } void CEditListElement::FreeData( EDT_ListData *pData ){ if( pData->pExtra ) XP_FREE( pData->pExtra ); XP_FREEIF(pData->pBaseURL); XP_FREE( pData ); } void CEditListElement::SetData( EDT_ListData *pData ){ char *pNew = 0; // Copy in pBaseURL. XP_FREEIF(m_pBaseURL); if (pData->pBaseURL && *pData->pBaseURL) { m_pBaseURL = XP_STRDUP(pData->pBaseURL); } SetType( pData->iTagType ); if( pData->bCompact ){ pNew = PR_sprintf_append( pNew, " COMPACT"); } if( pData->iStart != 1 ){ pNew = PR_sprintf_append( pNew, " START=%d", pData->iStart ); } switch( pData->eType ){ case ED_LIST_TYPE_DEFAULT: break; case ED_LIST_TYPE_DIGIT: SetType( P_NUM_LIST ); break; case ED_LIST_TYPE_BIG_ROMAN: SetType( P_NUM_LIST ); pNew = PR_sprintf_append( pNew, " TYPE=I" ); break; case ED_LIST_TYPE_SMALL_ROMAN: SetType( P_NUM_LIST ); pNew = PR_sprintf_append( pNew, " TYPE=i" ); break; case ED_LIST_TYPE_BIG_LETTERS: SetType( P_NUM_LIST ); pNew = PR_sprintf_append( pNew, " TYPE=A" ); break; case ED_LIST_TYPE_SMALL_LETTERS: SetType( P_NUM_LIST ); pNew = PR_sprintf_append( pNew, " TYPE=a" ); break; case ED_LIST_TYPE_CIRCLE: SetType( P_UNUM_LIST ); pNew = PR_sprintf_append( pNew, " TYPE=CIRCLE" ); break; case ED_LIST_TYPE_SQUARE: SetType( P_UNUM_LIST ); pNew = PR_sprintf_append( pNew, " TYPE=SQUARE" ); break; case ED_LIST_TYPE_DISC: SetType( P_UNUM_LIST ); pNew = PR_sprintf_append( pNew, " TYPE=DISC" ); break; case ED_LIST_TYPE_CITE: SetType( P_BLOCKQUOTE ); pNew = PR_sprintf_append( pNew, " TYPE=CITE" ); break; } if( pData->pExtra ){ pNew = PR_sprintf_append( pNew, " %s", pData->pExtra ); } if( pNew ){ pNew = PR_sprintf_append( pNew, ">" ); } SetTagData( pNew ); XP_FREEIF(pNew); } EDT_ListData* CEditListElement::GetData( ){ EDT_ListData *pRet; PA_Tag* pTag = TagOpen(0); pRet = ParseParams( pTag, GetWinCSID() ); PA_FreeTag( pTag ); if (pRet && m_pBaseURL) { pRet->pBaseURL = XP_STRDUP(m_pBaseURL); } return pRet; } static char* listParams[] = { PARAM_TYPE, PARAM_COMPACT, PARAM_START, 0 }; EDT_ListData* CEditListElement::ParseParams( PA_Tag *pTag, int16 csid ){ EDT_ListData *pData = NewData(); char *pType; pData->iTagType = pTag->type; pData->bCompact = edt_FetchParamBoolExist( pTag, PARAM_COMPACT, csid ); pType = edt_FetchParamString( pTag, PARAM_TYPE, csid ); if( pType == 0 ){ if( pTag->type == P_NUM_LIST ){ pData->eType = ED_LIST_TYPE_DIGIT; } else { pData->eType = ED_LIST_TYPE_DEFAULT; } } else { switch (*pType){ case 'A': pData->eType = ED_LIST_TYPE_BIG_LETTERS; break; case 'a': pData->eType = ED_LIST_TYPE_SMALL_LETTERS; break; case 'i': pData->eType = ED_LIST_TYPE_SMALL_ROMAN; break; case 'I': pData->eType = ED_LIST_TYPE_BIG_ROMAN; break; case 'c': case 'C': if ( XP_STRNCASECMP(pType, "cite", 4) == 0) { pData->eType = ED_LIST_TYPE_CITE; } else { pData->eType = ED_LIST_TYPE_CIRCLE; } break; case 'd': case 'D': pData->eType = ED_LIST_TYPE_DISC; break; case 's': case 'S': pData->eType = ED_LIST_TYPE_SQUARE; break; default: // To do: Figure out how to preserve unknown list types. pData->eType = ED_LIST_TYPE_DEFAULT; break; } } pData->iStart = edt_FetchParamInt( pTag, PARAM_START, 1, csid ); pData->pExtra = edt_FetchParamExtras( pTag, listParams, csid ); return pData; } void CEditListElement::CopyData(CEditListElement* pOther){ EDT_ListData* pData = pOther->GetData(); SetData(pData); FreeData(pData); } XP_Bool CEditListElement::IsMailQuote(){ XP_Bool bResult = FALSE; EDT_ListData* pData = GetData(); if ( pData->iTagType == P_BLOCKQUOTE && pData->eType == ED_LIST_TYPE_CITE ) { bResult = TRUE; } FreeData(pData); return bResult; } #ifdef DEBUG void CEditListElement::ValidateTree(){ CEditElement::ValidateTree(); // Must have at least one child. XP_ASSERT(GetChild()); // We rely on the children to check that // they can have a list as a parent. } #endif EEditElementType CEditListElement::GetElementType(){ return eListElement; } //----------------------------------------------------------------------------- // CEditTextElement //----------------------------------------------------------------------------- CEditTextElement::CEditTextElement(CEditElement *pParent, char *pText): CEditLeafElement(pParent, P_TEXT), m_pFirstLayoutElement(0), m_pTextBlock(0), m_tf(TF_NONE), m_iFontSize(DEF_FONTSIZE), m_href(ED_LINK_ID_NONE), m_color(ED_Color::GetUndefined()), m_pFace(NULL), m_iWeight(ED_FONT_WEIGHT_NORMAL), m_iPointSize(ED_FONT_POINT_SIZE_DEFAULT), m_pScriptExtra(NULL) { if( pText && *pText ){ m_pText = XP_STRDUP(pText); // should be new?? m_textSize = XP_STRLEN(m_pText)+1; } else { m_pText = 0; m_textSize = 0; } } CEditTextElement::CEditTextElement( IStreamIn *pIn, CEditBuffer* pBuffer ): CEditLeafElement(pIn, pBuffer), m_pFirstLayoutElement(0), m_pTextBlock(0), m_tf(TF_NONE), m_iFontSize(DEF_FONTSIZE), m_href(ED_LINK_ID_NONE), m_pText(0), m_color(ED_Color::GetUndefined()), m_pFace(NULL), m_pScriptExtra(NULL) { m_tf = (ED_TextFormat)pIn->ReadInt( ); m_iFontSize = pIn->ReadInt( ); m_color = pIn->ReadInt( ); if( m_tf & TF_HREF ){ char* pHref = pIn->ReadZString(); char* pExtra = pIn->ReadZString(); SetHREF( pBuffer->linkManager.Add( pHref, pExtra )); XP_FREE( pHref ); XP_FREE( pExtra ); } if ( m_tf & TF_FONT_FACE ) { m_pFace = pIn->ReadZString(); } if ( m_tf & TF_FONT_WEIGHT ) { m_iWeight = (int16) pIn->ReadInt(); } if ( m_tf & TF_FONT_POINT_SIZE ) { m_iPointSize = (int16) pIn->ReadInt(); } if ( EDT_IS_SCRIPT(m_tf) ) { m_pScriptExtra = pIn->ReadZString(); } m_pText = pIn->ReadZString(); if( m_pText ){ m_textSize = XP_STRLEN( m_pText ); } else { m_textSize = 0; } } CEditTextElement::~CEditTextElement(){ DisconnectLayoutElements((LO_Element*) m_pFirstLayoutElement); if ( m_pText) XP_FREE(m_pText); if( m_href != ED_LINK_ID_NONE ) m_href->Release(); if ( m_pFace ) { XP_FREE(m_pFace); m_pFace = NULL; } if ( m_pScriptExtra ) { XP_FREE(m_pScriptExtra); m_pScriptExtra = NULL; } } XP_Bool CEditTextElement::GetLOElementAndOffset( ElementOffset iEditOffset, XP_Bool bEditStickyAfter, LO_Element*& pRetText, int& pLayoutOffset ){ return GetLOTextAndOffset( iEditOffset, bEditStickyAfter, *(LO_TextStruct**)&pRetText, pLayoutOffset); } void CEditTextElement::StreamOut( IStreamOut *pOut ){ CEditSelection all; GetAll(all); PartialStreamOut(pOut, all ); } void CEditTextElement::PartialStreamOut( IStreamOut *pOut, CEditSelection& selection ){ CEditSelection local; if ( ClipToMe(selection, local) ) { // Fake a stream out. CEditLeafElement::StreamOut( pOut ); pOut->WriteInt( m_tf ); pOut->WriteInt( m_iFontSize ); pOut->WriteInt( m_color.GetAsLong() ); if( m_tf & TF_HREF ) { // Must make URLs absolute when copying CEditBuffer *pBuffer = GetEditBuffer(); char *pAbsolute = NULL; if( pBuffer ) pAbsolute = NET_MakeAbsoluteURL(LO_GetBaseURL(pBuffer->m_pContext), m_href->hrefStr); if( pAbsolute ) { pOut->WriteZString( pAbsolute ); XP_FREE(pAbsolute); } else pOut->WriteZString( m_href->hrefStr ); pOut->WriteZString( m_href->pExtra ); } if( m_tf & TF_FONT_FACE ){ pOut->WriteZString( m_pFace ); } if( m_tf & TF_FONT_WEIGHT ){ pOut->WriteInt(m_iWeight); } if( m_tf & TF_FONT_POINT_SIZE ){ pOut->WriteInt(m_iPointSize); } if( EDT_IS_SCRIPT(m_tf) ){ pOut->WriteZString( m_pScriptExtra ); } pOut->WritePartialZString( m_pText, local.m_start.m_iPos, local.m_end.m_iPos); // No children pOut->WriteInt((int32)eElementNone); } } EEditElementType CEditTextElement::GetElementType() { return eTextElement; } void CEditTextElement::SetLayoutElement( intn iEditOffset, intn lo_type, LO_Element* pLoElement ){ /* this element might be our text block. if so, we want to keep track of it */ if ( lo_type == LO_TEXTBLOCK ){ #if defined( DEBUG_shannon ) XP_TRACE(("Setting text block to %lx", pLoElement)); #endif XP_ASSERT ( pLoElement != NULL ); m_pTextBlock = &pLoElement->lo_textBlock; pLoElement->lo_any.edit_element = this; pLoElement->lo_any.edit_offset = iEditOffset; } SetLayoutElementHelper(LO_TEXT, (LO_Element**) &m_pFirstLayoutElement, iEditOffset, lo_type, pLoElement); } LO_Element* CEditTextElement::GetLayoutElement() { return m_pFirstLayoutElement; } void CEditTextElement::ResetLayoutElement( intn iEditOffset, LO_Element* pLoElement ){ ResetLayoutElementHelper((LO_Element**) &m_pFirstLayoutElement, iEditOffset, pLoElement); } void edt_RemoveNBSP( int16 csid, char *pString ){ while( pString && *pString ){ if( *pString == NON_BREAKING_SPACE ){ *pString = ' '; } pString = INTL_NextChar(csid, pString); } } void CEditTextElement::SetText( char *pText, XP_Bool bConvertSpaces, int16 csid){ if( m_pText ){ XP_FREE( m_pText ); } if( pText && *pText ){ m_pText = XP_STRDUP(pText); // should be new?? m_textSize = XP_STRLEN(m_pText)+1; if( bConvertSpaces ){ edt_RemoveNBSP( csid, m_pText ); } } else { m_pText = 0; m_textSize = 0; } } void CEditTextElement::SetColor( ED_Color iColor ){ m_color = iColor; // Dont set color if we are a script if(!iColor.IsDefined() || EDT_IS_SCRIPT(m_tf) ){ m_tf &= ~TF_FONT_COLOR; } else { m_tf |= TF_FONT_COLOR; } } // We seem to have problems when point size gets REALLY big, // so limit this for now #define ED_MAX_POINT_SIZE 104 // Boost the size changes when increasing or decreasing point sizes static void AdjustPointSizeChange( int& iChange, int iPointSize ) { if( iChange == 1 ) { if( iPointSize >= 10 && iPointSize <= 24 ) iChange = 2; else if( iPointSize > 24 ) iChange = 4; } else if( iChange == -1 ) { if( iPointSize >= 10 && iPointSize <= 24 ) iChange = -2; else if( iPointSize > 24 ) iChange = -4; } } void CEditTextElement::SetFontSize( int iSize, XP_Bool bRelative ) { //Override (use default for) ALL font size requests for Java Script if( EDT_IS_SCRIPT(m_tf) ){ iSize = 0; } else { // Calculate the new size and keep within allowable range // bRelative = TRUE means that iSize is a change relative // to current size if( bRelative ) { if( m_iPointSize != ED_FONT_POINT_SIZE_DEFAULT ) { // We are already using pts, so change that instead of m_iFontSize AdjustPointSizeChange( iSize, m_iPointSize ); SetFontPointSize(min(ED_MAX_POINT_SIZE, max(MIN_POINT_SIZE, m_iPointSize+iSize))); return; } #ifdef XP_WIN // EXPERIMENTAL: // If we are trying to increase past the highest HTML size (7), // we change over to POINT size instead so we can // continue to increase the size. // To do this, we need to get the point size of the "7" font, if( (m_iFontSize + iSize) > MAX_FONT_SIZE ) { int16 iPointSize = FE_CalcFontPointSize(GetEditBuffer()->m_pContext, MAX_FONT_SIZE, m_tf & TF_FIXED); if( iPointSize ) { AdjustPointSizeChange( iSize, iPointSize ); SetFontPointSize(min(ED_MAX_POINT_SIZE, iPointSize+iSize)); return; } } #endif //XP_WIN // We are using relative scale, // set new size relative to current iSize = m_iFontSize + iSize; } } iSize = min(MAX_FONT_SIZE, max(MIN_FONT_SIZE,(iSize))); // No change from current state if( m_iFontSize == iSize ) return; m_iFontSize = iSize; // Use 0 (or less) to signify changing to default if( iSize <= 0 ){ m_iFontSize = GetDefaultFontSize(); } if( m_iFontSize == GetDefaultFontSize() ){ m_tf &= ~TF_FONT_SIZE; } else { m_tf |= TF_FONT_SIZE; } // If using relative font size, then absolute point size must be default // Note that we do this even if setting to default size, // so this overrides any existing point size m_tf &= ~TF_FONT_POINT_SIZE; m_iPointSize = ED_FONT_POINT_SIZE_DEFAULT; } void CEditTextElement::SetFontFace( char* pFace ){ if ( m_pFace ) { XP_FREE(m_pFace); m_pFace = NULL; m_tf &= ~TF_FONT_FACE; } if ( pFace ) { m_tf |= TF_FONT_FACE; // Skip over initial separator signal if( *pFace == '_' ){ pFace++; } m_pFace = XP_STRDUP(pFace); } } void CEditTextElement::SetFontWeight( int16 weight ){ if ( m_iWeight != ED_FONT_WEIGHT_NORMAL ) { m_tf &= ~TF_FONT_WEIGHT; m_iWeight = ED_FONT_WEIGHT_NORMAL; } if ( weight != ED_FONT_WEIGHT_NORMAL ) { m_tf |= TF_FONT_WEIGHT; m_iWeight = weight; } } void CEditTextElement::SetFontPointSize( int16 pointSize ){ if ( m_iPointSize != ED_FONT_POINT_SIZE_DEFAULT ) { m_tf &= ~TF_FONT_POINT_SIZE; m_iPointSize = ED_FONT_POINT_SIZE_DEFAULT; } if ( pointSize != ED_FONT_POINT_SIZE_DEFAULT ) { m_tf |= TF_FONT_POINT_SIZE; m_iPointSize = pointSize; // If using absolute point size, then relative font size must be default m_iFontSize = GetDefaultFontSize(); m_tf &= ~TF_FONT_SIZE; } } void CEditTextElement::SetScriptExtra( char* pScriptExtra ){ if ( m_pScriptExtra ) { XP_FREE(m_pScriptExtra); m_pScriptExtra = NULL; } if ( pScriptExtra ) { m_pScriptExtra = XP_STRDUP(pScriptExtra); } } void CEditTextElement::SetHREF( ED_LinkId id ){ if( m_href != ED_LINK_ID_NONE ){ m_href->Release(); } m_href = id; // Dont set HREF if we are a script if( id != ED_LINK_ID_NONE && !EDT_IS_SCRIPT(m_tf) ){ id->AddRef(); m_tf |= TF_HREF; } else { m_tf &= ~TF_HREF; } } int CEditTextElement::GetFontSize(){ if( m_tf & TF_FONT_SIZE ){ return m_iFontSize; } else { return GetDefaultFontSize(); } } char* CEditTextElement::GetFontFace(){ return m_pFace; } int16 CEditTextElement::GetFontWeight(){ return m_iWeight; } int16 CEditTextElement::GetFontPointSize(){ return m_iPointSize; } char* CEditTextElement::GetScriptExtra(){ return m_pScriptExtra; } // // The the current text buffer but convert spaces to non breaking spaces where // necessary so the file can be laid out properly. // // The rules for converting spaces into Non breaking spaces are as follows: // // X = character // N = Non breaking space // S = Space // ^ = End of line // // 1 2 N // --------------------------------------- // Line Start N NS NNNS // Middle XS XNS XNNNS // End N^ XNN^ XNNN^ // // char* CEditTextElement::GetTextWithConvertedSpaces(){ // If regular get text doesn't do anything, neither should we. if( GetText() == 0 ){ return 0; } // grab a working buffer to copy the data into char *pBuf = edt_WorkBuf( GetLen()+1 ); // Out of memory if( pBuf == 0 ) return 0; CEditElement *pPrev = PreviousLeafInContainer(); CEditElement *pNext = LeafInContainerAfter(); XP_Bool bNextIsSpace, bNextIsEOL, bIsSpace, bAtBeginOfLine; // if we are the beginning of a line bAtBeginOfLine = ( pPrev == 0 || pPrev->CausesBreakAfter() ); char *pDest = pBuf; const char *pSrc = GetText(); while( *pSrc ) { bIsSpace = (*pSrc == ' '); if( pSrc[1] != 0 ){ bNextIsEOL = FALSE; bNextIsSpace = (pSrc[1] == ' '); } else if( pNext && pNext->IsText() && pNext->Text()->GetText()) { bNextIsEOL = FALSE; bNextIsSpace = ( pNext->Text()->GetText()[0] == ' ' ); } else { bNextIsEOL = TRUE; bNextIsSpace = FALSE; } if( bAtBeginOfLine ){ if( bIsSpace ){ *pDest = (char) NON_BREAKING_SPACE; } else { *pDest = *pSrc; } bAtBeginOfLine = FALSE; } else { if( bIsSpace && ( bNextIsSpace || bNextIsEOL )){ *pDest = (char) NON_BREAKING_SPACE; } else { *pDest = *pSrc; } } pSrc++; pDest++; } *pDest = 0; return pBuf; } void CEditTextElement::ClearFormatting(){ if ( m_tf & TF_HREF) { if ( m_href != ED_LINK_ID_NONE ) { m_href->Release(); } else { XP_ASSERT(FALSE); } } m_href = ED_LINK_ID_NONE; m_tf = 0; SetFontSize( GetDefaultFontSize() ); m_color = ED_Color::GetUndefined(); m_iWeight = ED_FONT_WEIGHT_NORMAL; m_iPointSize = ED_FONT_POINT_SIZE_DEFAULT; /* We don't preserve the script extra because the SCRIPT attribute is cleared. */ SetScriptExtra(NULL); } void CEditTextElement::SetData( EDT_CharacterData *pData ){ // Either Java Script type ED_TextFormat tfJava = TF_SERVER | TF_SCRIPT | TF_STYLE; // if we are setting server or script, // then it is the only thing we need to do if( (pData->mask & pData->values ) & tfJava ){ ClearFormatting(); #ifdef USE_SCRIPT m_tf = (pData->mask & pData->values ) & tfJava; return; #else XP_ASSERT(FALSE); // Front ends should not be setting these styles any more. return; #endif } #ifdef USE_SCRIPT // If we are already a Java Script type, // and we are NOT removing that type (mask bit is not set) // then ignore all other attributes if( (0 != (m_tf & TF_SERVER) && 0 == (pData->mask & TF_SERVER)) || (0 != (m_tf & TF_STYLE) && 0 == (pData->mask & TF_STYLE)) || (0 != (m_tf & TF_SCRIPT) && 0 == (pData->mask & TF_SCRIPT)) ){ return; } #endif if( pData->mask & TF_HREF ){ SetHREF( pData->linkId ); } if( pData->mask & TF_FONT_COLOR ){ if( (pData->values & TF_FONT_COLOR) ){ SetColor( EDT_LO_COLOR(pData->pColor) ); } else { SetColor( ED_Color::GetUndefined() ); } } if( pData->mask & TF_FONT_SIZE ){ if( pData->values & TF_FONT_SIZE ){ SetFontSize( pData->iSize ); } else { SetFontSize( GetDefaultFontSize() ); XP_ASSERT( (m_tf & TF_FONT_SIZE) == 0 ); } } if( pData->mask & TF_FONT_FACE ){ if( (pData->values & TF_FONT_FACE) ){ SetFontFace( pData->pFontFace ); } else { SetFontFace( 0 ); } } if( pData->mask & TF_FONT_WEIGHT ){ if( (pData->values & TF_FONT_WEIGHT) ){ SetFontWeight( pData->iWeight ); } else { SetFontWeight( ED_FONT_WEIGHT_NORMAL ); } } if( pData->mask & TF_FONT_POINT_SIZE ){ if( (pData->values & TF_FONT_POINT_SIZE) ){ SetFontPointSize( pData->iPointSize ); } else { SetFontPointSize( ED_FONT_POINT_SIZE_DEFAULT ); } } // remove the elements we've already handled. ED_TextFormat mask = pData->mask & ~( TF_HREF | TF_FONT_SIZE | TF_FONT_COLOR | TF_FONT_FACE | TF_FONT_WEIGHT | TF_FONT_POINT_SIZE #ifdef USE_SCRIPT | TF_SCRIPT | TF_SERVER | TF_STYLE ); #else ); #endif ED_TextFormat values = pData->values & mask; m_tf = (m_tf & ~mask) | values; } EDT_CharacterData* CEditTextElement::GetData(){ EDT_CharacterData *pData = EDT_NewCharacterData(); pData->mask = -1; pData->values = m_tf; pData->iSize = GetFontSize(); if( m_tf & TF_FONT_COLOR ){ pData->pColor = edt_MakeLoColor( GetColor() ); } if( m_tf & TF_HREF ){ if( m_href != ED_LINK_ID_NONE ){ pData->pHREFData = m_href->GetData(); } pData->linkId = m_href; } if( m_tf & TF_FONT_FACE ){ XP_ASSERT( m_pFace ); pData->pFontFace = XP_STRDUP( m_pFace ); } if ( m_tf & TF_FONT_WEIGHT ){ pData->iWeight = m_iWeight; } if ( m_tf & TF_FONT_POINT_SIZE ){ pData->iPointSize = m_iPointSize; } return pData; } void CEditTextElement::MaskData( EDT_CharacterData*& pData ){ if( pData == 0 ){ pData = GetData(); return; } ED_TextFormat differences = pData->values ^ m_tf; if( differences ){ // We are certain about all bits that are NOT different pData->mask &= ~differences; } // If mask bit is still set, then we THINK the attribute // is the same, but we don't know until we check the // color, href size, or font face directly if( (pData->mask & pData->values & m_tf & TF_FONT_COLOR) && EDT_LO_COLOR( pData->pColor ) != GetColor() ){ pData->mask &= ~TF_FONT_COLOR; XP_FREE( pData->pColor ); pData->pColor = 0; } if( (pData->mask & pData->values & m_tf & TF_HREF) && (pData->linkId != m_href ) ){ pData->mask &= ~TF_HREF; } if( (pData->mask & pData->values & m_tf & TF_FONT_SIZE) && (pData->iSize != m_iFontSize) ){ pData->mask &= ~TF_FONT_SIZE; } if( (pData->mask & pData->values & m_tf & TF_FONT_POINT_SIZE) && (pData->iPointSize != m_iPointSize) ){ pData->mask &= ~TF_FONT_POINT_SIZE; } if( (pData->mask & pData->values & m_tf & TF_FONT_FACE) ){ // Both have font face -- see if it is different char * pFontFace = GetFontFace(); // Note: String shouldn't be empty if bit is set! XP_ASSERT(pData->pFontFace); if( !pData->pFontFace && !pFontFace ){ // Both are empty so they are the same return; } if( pData->pFontFace && (!pFontFace || XP_STRCMP(pData->pFontFace, pFontFace )) ){ // Font name is different pData->mask &= ~TF_FONT_FACE; } } } // // insert a character into the text buffer. At some point we should make sure we // don't insert two spaces together. // XP_Bool CEditTextElement::InsertChar( int iOffset, int newChar ){ char buffer[2]; buffer[0] = newChar; buffer[1] = '\0'; return InsertChars(iOffset, buffer); } int32 CEditTextElement::InsertChars( int iOffset, char* pNewChars ){ // This is in bytes, not in characters. // However, it assumes we've gotten whole characters. int32 iNumBytes = pNewChars ? XP_STRLEN(pNewChars) : 0; if ( iNumBytes <= 0 ) { return 0; } // guarantee the buffer size // int32 iOldLen = GetLen(); int32 iSize = iOldLen + iNumBytes; if( iSize + 1 >= m_textSize ){ int newSize = GROW_TEXT( iSize + 1 ); char *pNewBuf = (char*)XP_ALLOC( newSize ); if( pNewBuf == 0 ){ // out of memory, should probably throw. return 0; } if( m_pText ){ XP_STRCPY( pNewBuf, m_pText ); XP_FREE( m_pText ); } else { pNewBuf[0] = 0; } m_pText = pNewBuf; m_textSize = newSize; } // // create an empty space in the string. We have to start from the end of // the string and move backward. Move the null, too // int32 i; for(i = iOldLen; i >= iOffset; i--){ m_pText[i+iNumBytes] = m_pText[i]; } // Insert the new text into the string. for(i = 0; i < iNumBytes; i++){ m_pText[iOffset+i] = pNewChars[i]; } /* update the text block with the new text */ if ( m_pTextBlock != NULL ){ lo_ChangeText ( m_pTextBlock, GetTextWithConvertedSpaces() ); } return iNumBytes; } void CEditTextElement::DeleteChar( MWContext *pContext, int iOffset ){ INTL_CharSetInfo csi = LO_GetDocumentCharacterSetInfo(pContext); int i = iOffset; char c = 1; int clen; clen = INTL_CharLen(INTL_GetCSIWinCSID(csi), (unsigned char *)m_pText+iOffset); if( m_pText[iOffset] ){ while( c != 0 ){ m_pText[i] = c = m_pText[i+clen]; i++; } } } char* CEditTextElement::DebugFormat(){ static char buf[1024]; strcpy(buf, "( " ); int i; if(m_tf & TF_BOLD) strcat(buf,"B "); if(m_tf & TF_ITALIC) strcat(buf,"I "); if(m_tf & TF_FIXED) strcat(buf,"TT "); if(m_tf & TF_SUPER) strcat(buf,"SUP "); if(m_tf & TF_SUB) strcat(buf,"SUB "); if(m_tf & TF_STRIKEOUT) strcat(buf,"SO "); if(m_tf & TF_UNDERLINE) strcat(buf,"U "); if(m_tf & TF_BLINK) strcat(buf,"BL "); #ifdef USE_SCRIPT if(m_tf & TF_SERVER) strcat(buf,"SERVER "); if(m_tf & TF_SCRIPT) strcat(buf,"SCRIPT "); if(m_tf & TF_STYLE) strcat(buf,"STYLE "); #endif if(m_tf & TF_SPELL) strcat(buf,"SPELL "); if(m_tf & TF_INLINEINPUT) strcat(buf,"INLINEINPUT "); if(m_tf & TF_INLINEINPUTTHICK) strcat(buf,"INLINEINPUTTHICK "); if(m_tf & TF_INLINEINPUTDOTTED) strcat(buf,"INLINEINPUTDOTTED "); if(m_pScriptExtra){ strcat(buf, m_pScriptExtra); strcat(buf," "); } if ( m_tf & TF_FONT_FACE && m_pFace ) { strcat( buf, m_pFace ); strcat( buf, " "); } if ( m_tf & TF_FONT_WEIGHT ){ char sizeBuf[30]; PR_snprintf(sizeBuf, 30, "weight=%d ", m_iWeight ); strcat( buf, sizeBuf); } if ( m_tf & TF_FONT_POINT_SIZE ){ char sizeBuf[30]; PR_snprintf(sizeBuf, 30, "pointsize=%d ", m_iPointSize ); strcat( buf, sizeBuf); } if( m_tf & TF_FONT_COLOR ){ char color_str[8]; PR_snprintf(color_str, 8, "#%06lX ", GetColor().GetAsLong() ); strcat( buf, color_str ); } if( m_tf & TF_FONT_SIZE ){ i = XP_STRLEN(buf); buf[i] = (GetFontSize() < 3 ? '-': '+'); buf[i+1] = '0' + abs(GetFontSize() - 3); buf[i+2] = ' '; buf[i+3] = 0; } if( m_tf & TF_HREF ){ strcat( buf, m_href->hrefStr ); } strcat(buf, ") " ); return buf; } // // Create a list of tags that represents the character formatting for this // text element. // void CEditTextElement::FormatOpenTags(PA_Tag*& pStart, PA_Tag*& pEnd){ if(m_tf & TF_BOLD){ edt_AddTag( pStart, pEnd, P_BOLD, FALSE ); } if(m_tf & TF_ITALIC){ edt_AddTag( pStart, pEnd, P_ITALIC, FALSE ); } if(m_tf & TF_FIXED){ edt_AddTag( pStart, pEnd, P_FIXED, FALSE ); } if(m_tf & TF_SUPER){ edt_AddTag( pStart,pEnd, P_SUPER, FALSE ); } if(m_tf & TF_SUB){ edt_AddTag( pStart,pEnd, P_SUB, FALSE ); } if(m_tf & TF_NOBREAK){ edt_AddTag( pStart,pEnd, P_NOBREAK, FALSE ); } if(m_tf & TF_STRIKEOUT){ edt_AddTag( pStart,pEnd, P_STRIKEOUT, FALSE ); } if(m_tf & TF_UNDERLINE){ edt_AddTag( pStart,pEnd, P_UNDERLINE, FALSE ); } if(m_tf & TF_BLINK){ edt_AddTag( pStart,pEnd, P_BLINK, FALSE ); } if(m_tf & TF_SPELL){ edt_AddTag( pStart,pEnd, P_SPELL, FALSE ); } if(m_tf & TF_INLINEINPUT){ edt_AddTag( pStart,pEnd, P_INLINEINPUT, FALSE ); } if(m_tf & TF_INLINEINPUTTHICK){ edt_AddTag( pStart,pEnd, P_INLINEINPUTTHICK, FALSE ); } if(m_tf & TF_INLINEINPUTDOTTED){ edt_AddTag( pStart,pEnd, P_INLINEINPUTDOTTED, FALSE ); } // Face and color have to come before size for old browsers. if( m_tf & TF_FONT_FACE ){ char buf[200]; XP_STRCPY( buf, " FACE=\"" ); strcat( buf, m_pFace ); strcat( buf, "\">" ); edt_AddTag( pStart,pEnd, P_FONT, FALSE, buf ); } if ( m_tf & TF_FONT_WEIGHT ) { char buf[20]; PR_snprintf(buf, 20, " FONT-WEIGHT=%d>", m_iWeight); edt_AddTag( pStart,pEnd, P_FONT, FALSE, buf ); } if ( m_tf & TF_FONT_POINT_SIZE ) { char buf[20]; PR_snprintf(buf, 20, " POINT-SIZE=%d>", m_iPointSize); edt_AddTag( pStart,pEnd, P_FONT, FALSE, buf ); } if( GetColor().IsDefined() ){ char buf[20]; PR_snprintf(buf, 20, "COLOR=\"#%06lX\">", GetColor().GetAsLong() ); edt_AddTag( pStart,pEnd, P_FONT, FALSE, buf ); } if( m_tf & TF_FONT_SIZE ){ char buf[20]; XP_STRCPY( buf, " SIZE=" ); int i = XP_STRLEN(buf); buf[i] = (GetFontSize() < 3 ? '-': '+'); buf[i+1] = '0' + abs(GetFontSize() - 3); buf[i+2] = '>'; buf[i+3] = 0; edt_AddTag( pStart,pEnd, P_FONT, FALSE, buf ); } if( m_tf & TF_HREF){ char *pBuf = PR_smprintf("HREF=%s %s\">", edt_MakeParamString(m_href->hrefStr), (m_href->pExtra ? m_href->pExtra : "") ); edt_AddTag( pStart,pEnd, P_ANCHOR, FALSE, pBuf ); free( pBuf ); } // if EVAL, hack the character attributes... #ifdef USE_SCRIPT if(m_tf & TF_SERVER){ edt_AddTag( pStart, pEnd, P_SERVER, FALSE ); return; } if(m_tf & TF_SCRIPT){ edt_AddTag( pStart, pEnd, P_SCRIPT, FALSE ); return; } if(m_tf & TF_STYLE){ edt_AddTag( pStart, pEnd, P_STYLE, FALSE ); return; } #endif } void CEditTextElement::FormatTransitionTags(CEditTextElement* /* pNext */, PA_Tag*& /* pStart */, PA_Tag*& /* pEnd */){ } void CEditTextElement::FormatCloseTags(PA_Tag*& pStart, PA_Tag*& pEnd){ // WARNING: order here is real important, Open and Close must match // exactly. // if EVAL must be last #ifdef USE_SCRIPT if(m_tf & TF_STYLE){ edt_AddTag( pStart, pEnd, P_STYLE, TRUE ); } if(m_tf & TF_SCRIPT){ edt_AddTag( pStart, pEnd, P_SCRIPT, TRUE ); } if(m_tf & TF_SERVER){ edt_AddTag( pStart, pEnd, P_SERVER, TRUE ); } #endif //if(m_href){ edt_AddTag( pStart,pEnd, P_ITALIC, FALSE ); } if( m_tf & TF_HREF ){ edt_AddTag( pStart,pEnd, P_ANCHOR, TRUE );} if( m_tf & TF_FONT_SIZE ){edt_AddTag( pStart,pEnd, P_FONT, TRUE );} if( GetColor().IsDefined() ){edt_AddTag( pStart,pEnd, P_FONT, TRUE );} if( m_tf & TF_FONT_POINT_SIZE ) edt_AddTag( pStart,pEnd, P_FONT, TRUE ); if( m_tf & TF_FONT_WEIGHT ) edt_AddTag( pStart,pEnd, P_FONT, TRUE ); if( m_tf & TF_FONT_FACE ) edt_AddTag( pStart,pEnd, P_FONT, TRUE ); if(m_tf & TF_INLINEINPUTDOTTED ){ edt_AddTag( pStart,pEnd, P_INLINEINPUTDOTTED, TRUE); } if(m_tf & TF_INLINEINPUTTHICK ){ edt_AddTag( pStart,pEnd, P_INLINEINPUTTHICK, TRUE); } if(m_tf & TF_INLINEINPUT ){ edt_AddTag( pStart,pEnd, P_INLINEINPUT, TRUE); } if(m_tf & TF_SPELL ){ edt_AddTag( pStart,pEnd, P_SPELL, TRUE); } if(m_tf & TF_BLINK ){ edt_AddTag( pStart,pEnd, P_BLINK, TRUE); } if(m_tf & TF_STRIKEOUT ){ edt_AddTag( pStart,pEnd, P_STRIKEOUT, TRUE); } if(m_tf & TF_UNDERLINE ){ edt_AddTag( pStart,pEnd, P_UNDERLINE, TRUE); } if(m_tf & TF_NOBREAK ){ edt_AddTag( pStart,pEnd, P_NOBREAK, TRUE); } if(m_tf & TF_SUB ){ edt_AddTag( pStart,pEnd, P_SUB, TRUE); } if(m_tf & TF_SUPER ){ edt_AddTag( pStart,pEnd, P_SUPER, TRUE); } if(m_tf & TF_FIXED ){ edt_AddTag( pStart, pEnd, P_FIXED, TRUE); } if(m_tf & TF_ITALIC ){ edt_AddTag( pStart, pEnd, P_ITALIC, TRUE); } if(m_tf & TF_BOLD ){ edt_AddTag( pStart, pEnd, P_BOLD, TRUE); } } // // Create a text tag. We are going to be feeding these tags to the layout // engine. The layout element this edit element points to is going to change. // PA_Tag* CEditTextElement::TagOpen( int iEditOffset ){ // LTNOTE: I think that we only want to do this if we are the first text // in the container, other wise, we don't do anything. PA_Tag *pStart = 0; PA_Tag *pEnd = 0; XP_Bool bFirst = IsFirstInContainer(); if( GetLen() == 0 && (!bFirst || (bFirst && LeafInContainerAfter() != 0))){ return 0; } PA_Tag *pTag = XP_NEW( PA_Tag ); XP_BZERO( pTag, sizeof( PA_Tag ) ); FormatOpenTags( pStart, pEnd ); if( pStart == 0 ){ pStart = pTag; } if( pEnd ){ pEnd->next = pTag; } pEnd = pTag; if(GetLen() == 0){ // As of 3.0b5, a non-breaking space always caused a memory leak when // it was laid out. See bug 23404 // So use a different character here. The character is // removed by code in CEditBuffer::Relayout #ifdef USE_PERIOD_FOR_BLANK_LAYOUT SetTagData(pTag, "."); #else SetTagData(pTag, NON_BREAKING_SPACE_STRING); #endif //SetTagData(pTag, ""); } else { SetTagData(pTag, GetTextWithConvertedSpaces()+iEditOffset); } if( iEditOffset==0 ){ m_pFirstLayoutElement = 0; } FormatCloseTags( pStart, pEnd ); return pStart; } XP_Bool CEditTextElement::GetLOTextAndOffset( ElementOffset iEditOffset, XP_Bool bStickyAfter, LO_TextStruct*& pRetText, int& iLayoutOffset ){ LO_TextStruct *pText; LO_Element* pLoElement = m_pFirstLayoutElement; XP_ASSERT(!pLoElement || pLoElement->lo_any.edit_offset == 0); intn len = GetLen(); while(pLoElement != 0){ if( pLoElement->type == LO_TEXT ){ // if we have scanned past our edit element, we can't find the // insert point. pText = &(pLoElement->lo_text); if( pText->edit_element != this){ /* assert(0) */ return FALSE; } int16 textLen = pText->text_len; int32 loEnd = pText->edit_offset + textLen; // // See if we are at the dreaded lopped of the last space case. // if( loEnd+1 == iEditOffset ){ // ok, we've scaned past the end of the Element if( len == iEditOffset && m_pText[iEditOffset-1] == ' '){ // we wraped to the next element. Return the next Text Element. CEditElement *pNext = FindNextElement(&CEditElement::FindText,0); if( pNext ){ return pNext->Text()->GetLOTextAndOffset( 0, FALSE, pRetText, iLayoutOffset ); } // we should have all these cases handled now. // jhp Nope. (a least as of Beta 2) If you have a very narrow document, // and you // type in two lines of text, and you're at the end of the // second line, and you type a space, and the // space causes a line wrap, then you end up here. // XP_ASSERT(FALSE); return FALSE; } } if( loEnd > iEditOffset || (loEnd == iEditOffset && (!bStickyAfter || len == iEditOffset || textLen == 0 ) ) ){ // we've found the right element, return the information. iLayoutOffset = iEditOffset - pText->edit_offset; pRetText = pText; return TRUE; } } pLoElement = pLoElement->lo_any.next; } return FALSE; } LO_TextStruct* CEditTextElement::GetLOText( int iEditOffset ){ LO_TextStruct *pText; LO_Element* pLoElement = m_pFirstLayoutElement; while(pLoElement != 0){ if( pLoElement->type == LO_TEXT ){ // if we have scanned past our edit element, we can't find the // insert point. pText = &(pLoElement->lo_text); if( pText->edit_element != this){ return 0; } if( pText->edit_offset > iEditOffset ){ return 0; } if( pText->edit_offset + pText->text_len >= iEditOffset ){ // we've found the right element, return the information. return pText; } } pLoElement = pLoElement->lo_any.next; } return 0; } LO_TextBlock* CEditTextElement::GetTextBlock() { return m_pTextBlock; } XP_Bool CEditTextElement::CanReflow() { // we can only reflow if we have a text block return m_pTextBlock != NULL; } void CEditTextElement::PrintTagOpen( CPrintState *pPrintState, TagType t, ED_TextFormat tf, char* pExtra ){ if ( ! pExtra ) pExtra = ">"; pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "<%s%s", EDT_TagString(t), pExtra ); pPrintState->m_formatStack.Push(tf); pPrintState->m_elementStack.Push(this); } void CEditTextElement::PrintFormatDifference( CPrintState *ps, ED_TextFormat bitsDifferent ){ if(bitsDifferent & TF_BOLD){ PrintTagOpen( ps, P_BOLD, TF_BOLD ); } if(bitsDifferent & TF_ITALIC){ PrintTagOpen( ps, P_ITALIC, TF_ITALIC ); } if(bitsDifferent & TF_FIXED){ PrintTagOpen( ps, P_FIXED, TF_FIXED ); } if(bitsDifferent & TF_SUPER){ PrintTagOpen( ps, P_SUPER, TF_SUPER ); } if(bitsDifferent & TF_SUB){ PrintTagOpen( ps, P_SUB, TF_SUB ); } if(bitsDifferent & TF_NOBREAK){ PrintTagOpen( ps, P_NOBREAK, TF_NOBREAK ); } if(bitsDifferent & TF_STRIKEOUT){ PrintTagOpen( ps, P_STRIKEOUT, TF_STRIKEOUT ); } if(bitsDifferent & TF_UNDERLINE){ PrintTagOpen( ps, P_UNDERLINE, TF_UNDERLINE ); } if(bitsDifferent & TF_BLINK){ PrintTagOpen( ps, P_BLINK, TF_BLINK ); } if(bitsDifferent & TF_SPELL){ /*do nothing*/ } if(bitsDifferent & TF_INLINEINPUT){ /*do nothing*/ } if(bitsDifferent & TF_INLINEINPUTTHICK){ /*do nothing*/ } if(bitsDifferent & TF_INLINEINPUTDOTTED){ /*do nothing*/ } // Face and color have to preceed FONT_SIZE, or else old browsers will // reset the font_size to zero when they encounter the unknown tags. if( bitsDifferent & TF_FONT_FACE ){ ps->m_iCharPos += ps->m_pOut->Printf( "", m_pFace); ps->m_formatStack.Push(TF_FONT_FACE); ps->m_elementStack.Push(this); } if( bitsDifferent & TF_FONT_WEIGHT ){ ps->m_iCharPos += ps->m_pOut->Printf( "", m_iWeight); ps->m_formatStack.Push(TF_FONT_WEIGHT); ps->m_elementStack.Push(this); } if( bitsDifferent & TF_FONT_POINT_SIZE ){ ps->m_iCharPos += ps->m_pOut->Printf( "", m_iPointSize); ps->m_formatStack.Push(TF_FONT_POINT_SIZE); ps->m_elementStack.Push(this); } if( bitsDifferent & TF_FONT_COLOR ){ ps->m_iCharPos += ps->m_pOut->Printf( "", GetColor().GetAsLong() ); ps->m_formatStack.Push(TF_FONT_COLOR); ps->m_elementStack.Push(this); } if( bitsDifferent & TF_FONT_SIZE ){ char buf[4]; buf[0] = (GetFontSize() < 3 ? '-': '+'); buf[1] = '0' + abs(GetFontSize() - 3); buf[2] = 0; ps->m_iCharPos += ps->m_pOut->Printf( "", buf); ps->m_formatStack.Push(TF_FONT_SIZE); ps->m_elementStack.Push(this); } if( bitsDifferent & TF_HREF){ // doesn't use the output routine because HREF can be large. char *pS1 = ""; char *pS2 = ""; if( m_href->pExtra ){ pS1 = " "; pS2 = m_href->pExtra; } ps->m_iCharPos += ps->m_pOut->Printf( "", edt_MakeParamString(m_href->hrefStr), pS1, pS2 ); ps->m_formatStack.Push(TF_HREF); ps->m_elementStack.Push(this); } #ifdef USE_SCRIPT if(bitsDifferent & TF_STYLE){ PrintTagOpen( ps, P_STYLE, TF_STYLE, GetScriptExtra() ); } if(bitsDifferent & TF_SCRIPT){ PrintTagOpen( ps, P_SCRIPT, TF_SCRIPT, GetScriptExtra() ); } if(bitsDifferent & TF_SERVER){ PrintTagOpen( ps, P_SERVER, TF_SERVER, GetScriptExtra() ); } #endif } void CEditTextElement::PrintFormat( CPrintState *ps, CEditTextElement *pFirst, ED_TextFormat mask ){ ED_TextFormat bitsCommon = 0; ED_TextFormat bitsDifferent = 0; ComputeDifference(pFirst, mask, bitsCommon, bitsDifferent); CEditTextElement *pNext = (CEditTextElement*) TextInContainerAfter(); if( bitsCommon ){ // While we're in a run of the same style, there's nothing to do. // Avoid unnescessary recursion so we don't blow the stack on Mac or Win16 while ( pNext && SameAttributes(pNext) ){ pNext = (CEditTextElement*) pNext->TextInContainerAfter(); } if( pNext ){ pNext->PrintFormat( ps, pFirst, bitsCommon ); } else { // we hit the end, so we have to open everything pFirst->PrintFormatDifference( ps, bitsCommon); } } if( bitsDifferent ){ pFirst->PrintFormatDifference( ps, bitsDifferent ); } } XP_Bool CEditTextElement::SameFormat(CEditTextElement *pCompare) { XP_Bool bSame = SameAttributes(pCompare); if( bSame ) { bSame = (m_tf == pCompare->m_tf) ? TRUE : FALSE; } return bSame; } XP_Bool CEditTextElement::SameAttributes(CEditTextElement *pCompare){ ED_TextFormat bitsCommon; ED_TextFormat bitsDifferent; ComputeDifference(pCompare, -1, bitsCommon, bitsDifferent); return ( bitsCommon == m_tf); } void CEditTextElement::ComputeDifference(CEditTextElement *pFirst, ED_TextFormat mask, ED_TextFormat& bitsCommon, ED_TextFormat& bitsDifferent){ bitsCommon = m_tf & mask; bitsDifferent = mask & ~bitsCommon; if( (bitsCommon & TF_FONT_SIZE)&& (GetFontSize() != pFirst->GetFontSize())){ bitsCommon &= ~TF_FONT_SIZE; bitsDifferent |= TF_FONT_SIZE; } if( (bitsCommon & TF_FONT_COLOR)&& (GetColor() != pFirst->GetColor())){ bitsCommon &= ~TF_FONT_COLOR; bitsDifferent |= TF_FONT_COLOR; } if( (bitsCommon & TF_FONT_POINT_SIZE)&& (GetFontPointSize() != pFirst->GetFontPointSize())){ bitsCommon &= ~TF_FONT_POINT_SIZE; bitsDifferent |= TF_FONT_POINT_SIZE; } if( (bitsCommon & TF_FONT_WEIGHT)&& (GetFontWeight() != pFirst->GetFontWeight())){ bitsCommon &= ~TF_FONT_WEIGHT; bitsDifferent |= TF_FONT_WEIGHT; } //CLM: These pointers were sometimes NULL char * pFontFace = GetFontFace(); char * pFirstFontFace = pFirst->GetFontFace(); // Assure that we have strings to compare for font face if( !pFontFace ){ pFontFace = " "; } if( !pFirstFontFace ){ pFirstFontFace = " "; } if( (bitsCommon & TF_FONT_FACE) && XP_STRCMP(pFontFace,pFirstFontFace) != 0 ){ bitsCommon &= ~TF_FONT_FACE; bitsDifferent |= TF_FONT_FACE; } if( bitsCommon & TF_FONT_FACE ) { char* a = GetFontFace(); char* b = pFirst->GetFontFace(); XP_Bool bA = a != NULL; XP_Bool bB = b != NULL; if ((bA ^ bB) || (bA && bB && XP_STRCMP(a, b) != 0)) { bitsCommon &= ~TF_FONT_FACE; bitsDifferent |= TF_FONT_FACE; } } if( (bitsCommon & TF_HREF)&& (GetHREF() != pFirst->GetHREF())){ bitsCommon &= ~TF_HREF; bitsDifferent |= TF_HREF; } } void CEditTextElement::PrintTagClose( CPrintState *ps, TagType t ){ ps->m_iCharPos += ps->m_pOut->Printf( "", EDT_TagString(t) ); } void CEditTextElement::PrintPopFormat( CPrintState *ps, int iStackTop ){ while( ps->m_formatStack.m_iTop >= iStackTop ){ ED_TextFormat tf = (ED_TextFormat) ps->m_formatStack.Pop(); ps->m_elementStack.Pop(); #ifdef USE_SCRIPT if( tf & TF_STYLE){ PrintTagClose( ps, P_STYLE ); } if( tf & TF_SCRIPT){ PrintTagClose( ps, P_SCRIPT ); } if( tf & TF_SERVER){ PrintTagClose( ps, P_SERVER ); } #endif if( tf & TF_BOLD){ PrintTagClose( ps, P_BOLD ); } if( tf & TF_ITALIC){ PrintTagClose( ps, P_ITALIC ); } if( tf & TF_FIXED){ PrintTagClose( ps, P_FIXED ); } if( tf & TF_SUPER){ PrintTagClose( ps, P_SUPER ); } if( tf & TF_SUB){ PrintTagClose( ps, P_SUB ); } if( tf & TF_NOBREAK){ PrintTagClose( ps, P_NOBREAK ); } if( tf & TF_STRIKEOUT){ PrintTagClose( ps, P_STRIKEOUT ); } if( tf & TF_UNDERLINE){ PrintTagClose( ps, P_UNDERLINE ); } if( tf & TF_BLINK){ PrintTagClose( ps, P_BLINK ); } if( tf & TF_SPELL){ /* do nothing */ } if( tf & TF_INLINEINPUT){ /* do nothing */ } if( tf & TF_INLINEINPUTTHICK){ /* do nothing */ } if( tf & TF_INLINEINPUTDOTTED){ /* do nothing */ } if( tf & TF_FONT_SIZE){ PrintTagClose( ps, P_FONT ); } if( tf & TF_FONT_COLOR){ PrintTagClose( ps, P_FONT ); } if( tf & TF_FONT_POINT_SIZE){ PrintTagClose( ps, P_FONT ); } if( tf & TF_FONT_WEIGHT){ PrintTagClose( ps, P_FONT ); } if( tf & TF_FONT_FACE){ PrintTagClose( ps, P_FONT ); } if( tf & TF_HREF){ PrintTagClose( ps, P_ANCHOR ); } } } ED_TextFormat CEditTextElement::PrintFormatClose( CPrintState *ps ){ XP_Bool bDiff = FALSE; ED_TextFormat bitsNeedFormat = m_tf ; if( ps->m_formatStack.IsEmpty()){ return bitsNeedFormat; } int i = -1; while( !bDiff && ++i <= ps->m_formatStack.m_iTop ){ ED_TextFormat tf = (ED_TextFormat) ps->m_formatStack[i]; if( m_tf & tf ){ if( tf == TF_FONT_COLOR && GetColor() != ps->m_elementStack[i]->GetColor()){ bDiff = TRUE; } else if( tf == TF_FONT_SIZE && GetFontSize() != ps->m_elementStack[i]->GetFontSize()){ bDiff = TRUE; } else if( tf == TF_FONT_POINT_SIZE && GetFontPointSize() != ps->m_elementStack[i]->GetFontPointSize()){ bDiff = TRUE; } else if( tf == TF_FONT_WEIGHT && GetFontWeight() != ps->m_elementStack[i]->GetFontWeight()){ bDiff = TRUE; } else if( tf == TF_FONT_FACE && XP_STRCMP(GetFontFace(), ps->m_elementStack[i]->GetFontFace()) != 0){ bDiff = TRUE; } else if( tf == TF_HREF && GetHREF() != ps->m_elementStack[i]->GetHREF() ){ bDiff = TRUE; } } else { bDiff = TRUE; } // if the stuff is on the stack, then it doesn't need to be formatted if( !bDiff ){ bitsNeedFormat &= ~tf; } } PrintPopFormat( ps, i ); return bitsNeedFormat; } // for use within void CEditTextElement::PrintWithEscapes( CPrintState *ps, XP_Bool bTrimTrailingSpaces ){ char *p = GetTextWithConvertedSpaces(); int csid = INTL_DefaultWinCharSetID(ps->m_pBuffer->m_pContext); // Trim the last non-breaking spaces at the end of a // top-level paragraph. The spaces just irritate users, // especially in HTML mail. // (But don't trim the last nbsp if it's the only thing in the paragraph, or // else layout will ignore the paragraph.) // And don't trim them in tables, where they're used to affect column width. if ( bTrimTrailingSpaces && p && ! LeafInContainerAfter() && ! GetSubDocSkipRoot() ) { /* find last NBSP by going forward char-by-char */ char *s, *last_nbsp, *end; s = last_nbsp = p; end = p + XP_STRLEN(p); while ((s <= end) && (*s)) { /* if not nbsp then advance both pointers */ if (*s != NON_BREAKING_SPACE) last_nbsp = s = INTL_NextChar(csid, s); else s = INTL_NextChar(csid, s); } if (last_nbsp <= end){ // Don't trim last nbsp if it's the only thing in the paragraph. if ( p == last_nbsp && IsFirstInContainer() && last_nbsp < end ){ last_nbsp++; } *last_nbsp = '\0'; /* points to last nbsp or end of string */ } } if( p == 0 ){ #ifndef EDT_DDT p = NON_BREAKING_SPACE_STRING; #endif } edt_PrintWithEscapes( ps, p, !InFormattedText() ); } void CEditTextElement::PrintLiteral( CPrintState *ps ){ int iLen = GetLen(); ps->m_pOut->Write( GetText(), iLen ); ps->m_iCharPos += iLen; } void CEditTextElement::PrintOpen( CPrintState *ps){ ED_TextFormat bitsToFormat = PrintFormatClose( ps ); CEditTextElement *pNext = (CEditTextElement*) TextInContainerAfter(); if( pNext ){ pNext->PrintFormat( ps, this, bitsToFormat ); } else { PrintFormatDifference( ps, bitsToFormat ); } if ( ps->ShouldPrintSelectionComments(this) ){ // Up to three pieces int32 index = 0; if ( ps->ShouldPrintSelectionComment(this, FALSE) ){ index = ps->m_selection.m_start.m_iPos; if ( 0 < index ) { PrintRange(ps, 0, index); } ps->PrintSelectionComment(FALSE, ps->m_selection.m_start.m_bStickyAfter); } if ( ps->ShouldPrintSelectionComment(this, TRUE) ){ int32 oldIndex = index; index = ps->m_selection.m_end.m_iPos; if ( oldIndex < index ) { PrintRange(ps, oldIndex, index); } ps->PrintSelectionComment(TRUE, ps->m_selection.m_end.m_bStickyAfter); } if ( index < GetLen() ){ PrintRange(ps, index, GetLen()); } } else { PrintOpen2(ps, TRUE); } if( pNext == 0 ) { PrintPopFormat( ps, 0 ); } } void CEditTextElement::PrintRange( CPrintState *ps, int32 start, int32 end ){ // We modify m_pText in place and then call PrintOpen2 to do the // actual printing. (And then we restore m_pText to its original // condition.) // int32 len = GetLen(); if ( start == 0 && end == len ){ PrintOpen2(ps, FALSE); } else { if ( end > len ) { // This is getting annoying - XP_TRACE instead, // since we survive it just fine XP_TRACE(("PrintRange error: end = %d len = %d\n", end, len)); //XP_ASSERT(FALSE); #ifdef DEBUG_akkana printf("end = %d len = %d\n", end, len); #endif end = len; } if ( start < 0 ) { XP_ASSERT(FALSE); start = 0; } if ( start < end && m_pText ) { char* pText = m_pText; char save = pText[end]; pText[end] = '\0'; m_pText = pText + start; // If GetLen() is ever cached we would need to update the cache. XP_ASSERT(GetLen() == end - start); PrintOpen2(ps, FALSE); m_pText = pText; pText[end] = save; } } } void CEditTextElement::PrintOpen2( CPrintState *ps, XP_Bool bTrimTrailingSpaces ) { #ifdef USE_SCRIPT if( m_tf & (TF_SERVER|TF_SCRIPT|TF_STYLE) ){ PrintLiteral( ps ); } else { PrintWithEscapes( ps ); } #else PrintWithEscapes( ps, bTrimTrailingSpaces ); #endif } XP_Bool CEditTextElement::Reduce( CEditBuffer *pBuffer ){ if( GetLen() == 0 ){ if( pBuffer->m_pCurrent == this || (IsFirstInContainer() && LeafInContainerAfter() == 0) ){ return FALSE; } else { return TRUE; } } else { return FALSE; } } #ifdef DEBUG void CEditTextElement::ValidateTree(){ CEditLeafElement::ValidateTree(); LO_TextStruct *pText = (LO_TextStruct*) GetLayoutElement(); if( GetType() != P_TEXT ){ XP_ASSERT(FALSE); } if(m_pText && pText ) { if (pText->edit_element != this){ XP_ASSERT(FALSE); } if (pText->edit_offset != 0){ XP_ASSERT(FALSE); } } XP_Bool bHasLinkTag = (m_tf & TF_HREF) != 0; XP_Bool bHasLinkRef = (m_href != ED_LINK_ID_NONE); if( bHasLinkTag != bHasLinkRef ){ XP_ASSERT(FALSE); }; } #endif // DEBUG void CEditTextElement::StreamToPositionalText( IStreamOut *pOut, XP_Bool bEnd ){ if( !bEnd ){ char space = ' '; // Ignore Quoted text, unless there is a selection. if ( InMailQuote() && !EDT_IsSelected(GetEditBuffer()->m_pContext)) { // Write out spaces instead of text. This prevents us from spell checking // quoted text. int32 len = GetLen(); for(int32 i = 0; i < len; i++){ pOut->Write(&space, 1); } } else { INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(GetEditBuffer()->m_pContext); int16 csid = INTL_GetCSIWinCSID(c); if (INTL_CharSetType(csid) == SINGLEBYTE){ pOut->Write(GetText(),GetLen()); } else{ // Replace any multi-byte characters with spaces, to avoid spell checking // of Japanese and other far-eastern text. int32 textLen, charLen=0; char *pText = GetText(); for (textLen = GetLen(); textLen > 0; textLen -= charLen, pText += charLen){ charLen = INTL_CharLen(csid, (unsigned char *)pText); if (charLen > 1){ for(int32 i = 0; i < charLen; i++){ pOut->Write(&space, 1); } } else{ pOut->Write(pText, charLen); } } } } } } //----------------------------------------------------------------------------- // Insertion routines. //----------------------------------------------------------------------------- // // Split text object into two text objects. Parent is the same. // CEditElement* CEditTextElement::SplitText( int iOffset ){ CEditTextElement* pNew = new CEditTextElement( 0, m_pText+iOffset ); m_pText[iOffset] = 0; GetParent()->Split( this, pNew, &CEditElement::SplitContainerTest, 0 ); return pNew; } // Copy void CEditTextElement::CopyTextFormat( CEditTextElement *pTextElement ) { if( pTextElement ) { pTextElement->m_tf = m_tf; pTextElement->m_iFontSize = m_iFontSize; pTextElement->m_color = m_color; pTextElement->SetHREF(m_href); if ( m_pFace ) pTextElement->SetFontFace(m_pFace); pTextElement->m_iWeight = m_iWeight; pTextElement->m_iPointSize = m_iPointSize; if ( m_pScriptExtra ) pTextElement->SetScriptExtra(m_pScriptExtra); } } CEditTextElement* CEditTextElement::CopyEmptyText( CEditElement *pParent ) { CEditTextElement* pNew = new CEditTextElement(pParent, 0); CopyTextFormat(pNew); return pNew; } void CEditTextElement::DeleteText(){ CEditElement *pKill = this; CEditElement *pParent; do { pParent = pKill->GetParent(); pKill->Unlink(); delete pKill; pKill = pParent; } while( BitSet( edt_setCharFormat, pKill->GetType() ) && pKill->GetChild() == 0 ); } //---------------------------------------------------------------------------- // CEditImageElement //---------------------------------------------------------------------------- // // LTNOTE: these should be static functions on CEditImageData.. // EDT_ImageData* edt_NewImageData(){ EDT_ImageData *pData = XP_NEW( EDT_ImageData ); if( pData == 0 ){ // throw(); return pData; } pData->bIsMap = FALSE; // pData->pUseMap = 0; now in pExtra pData->align = ED_ALIGN_DEFAULT; pData->pSrc = 0; pData->pLowSrc = 0; pData->pName = 0; pData->pAlt = 0; pData->iWidth = 0; pData->iHeight = 0; pData->iOriginalWidth = 0; pData->iOriginalHeight = 0; pData->bWidthPercent = FALSE; pData->bHeightPercent = FALSE; pData->iHSpace = 0; pData->iVSpace = 0; pData->iBorder = -1; pData->bNoSave = FALSE; pData->pHREFData = NULL; pData->pExtra = 0; return pData; } EDT_ImageData* edt_DupImageData( EDT_ImageData *pOldData ){ EDT_ImageData *pData = XP_NEW( EDT_ImageData ); // pData->pUseMap = edt_StrDup(pOldData->pUseMap); now in pExtra pData->pSrc = edt_StrDup(pOldData->pSrc); pData->pName = edt_StrDup(pOldData->pName); pData->pLowSrc = edt_StrDup(pOldData->pLowSrc); pData->pAlt = edt_StrDup(pOldData->pAlt); pData->pExtra = edt_StrDup(pOldData->pExtra); pData->bIsMap = pOldData->bIsMap; pData->align = pOldData->align; pData->iWidth = pOldData->iWidth; pData->iHeight = pOldData->iHeight; pData->iOriginalWidth = pOldData->iOriginalWidth; pData->iOriginalHeight = pOldData->iOriginalHeight; pData->bWidthPercent = pOldData->bWidthPercent; pData->bHeightPercent = pOldData->bHeightPercent; pData->iHSpace = pOldData->iHSpace; pData->iVSpace = pOldData->iVSpace; pData->iBorder = pOldData->iBorder; pData->bNoSave = pOldData->bNoSave; if( pOldData->pHREFData ){ pData->pHREFData = EDT_DupHREFData( pOldData->pHREFData ); } else { pData->pHREFData = 0; } return pData; } void edt_FreeImageData( EDT_ImageData *pData ){ // if( pData->pUseMap ) XP_FREE( pData->pUseMap ); now in pExtra if( pData->pSrc ) XP_FREE( pData->pSrc ); if( pData->pLowSrc ) XP_FREE( pData->pLowSrc ); if( pData->pName ) XP_FREE( pData->pName ); if( pData->pAlt ) XP_FREE( pData->pAlt ); if( pData->pExtra ) XP_FREE( pData->pExtra ); if( pData->pHREFData ) EDT_FreeHREFData( pData->pHREFData ); XP_FREE( pData ); } // // Constructors, Streamers.. // CEditImageElement::CEditImageElement( CEditElement *pParent, PA_Tag* pTag, int16 csid, ED_LinkId href ): CEditLeafElement( pParent, P_IMAGE ), m_pLoImage(0), m_pParams(0), m_iHeight(0), m_iWidth(0), m_bWidthPercent(0), m_bHeightPercent(0), m_iSaveIndex(0), m_iSaveLowIndex(0), m_href(ED_LINK_ID_NONE), m_align( ED_ALIGN_DEFAULT), m_bSizeWasGiven(FALSE), m_bSizeIsBogus(FALSE) { // must set href before calling SetImageData to allow GetDefaultBorder to work if( href != ED_LINK_ID_NONE ){ SetHREF( href ); } if( pTag ){ EDT_ImageData *pData = ParseParams( pTag, csid ); SetImageData( pData ); edt_FreeImageData( pData ); } } CEditImageElement::CEditImageElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer): CEditLeafElement(pStreamIn, pBuffer), m_pLoImage(0), m_pParams(0), m_href(ED_LINK_ID_NONE), m_align(ED_ALIGN_DEFAULT) { m_align = (ED_Alignment) pStreamIn->ReadInt(); m_bSizeWasGiven = pStreamIn->ReadInt() ? TRUE : FALSE; m_bSizeIsBogus = pStreamIn->ReadInt() ? TRUE : FALSE; m_iHeight = pStreamIn->ReadInt(); m_iWidth = pStreamIn->ReadInt(); m_bWidthPercent = pStreamIn->ReadInt(); m_bHeightPercent = pStreamIn->ReadInt(); m_pParams = pStreamIn->ReadZString(); char *pHrefString = pStreamIn->ReadZString(); char *pExtra = pStreamIn->ReadZString(); if( pHrefString ){ SetHREF( pBuffer->linkManager.Add( pHrefString, pExtra )); XP_FREE( pHrefString ); if( pExtra ) XP_FREE( pExtra ); } } CEditImageElement::~CEditImageElement(){ DisconnectLayoutElements((LO_Element*) m_pLoImage); if( m_pParams) XP_FREE( m_pParams ); if( m_href != ED_LINK_ID_NONE ) m_href->Release(); } // Return TRUE if pURL has changed. PRIVATE XP_Bool edt_make_image_relative(char *pBase,char **pURL) { if (!pBase || !*pBase || !pURL || !*pURL || !**pURL) { // Base and pURL must be existing non-empty strings. return FALSE; } if ((*pURL)[0] == '/' || !EDT_IsImageURL(*pURL)) { // Don't change URL if using absolute pathing. // or not really an image file return FALSE; } char *pAbs = NET_MakeAbsoluteURL(pBase,*pURL); // In case pURL was already relative. char *pRel; NET_MakeRelativeURL(pBase,pAbs,&pRel); XP_FREEIF(pAbs); if (pRel && XP_STRCMP(pRel,*pURL)) { // pRel exists and is different than original value. XP_FREEIF(*pURL); *pURL = pRel; return TRUE; } else { XP_FREEIF(pRel); return FALSE; } } void CEditImageElement::SetLayoutElement( intn iEditOffset, intn lo_type, LO_Element* pLoElement ){ SetLayoutElementHelper(LO_IMAGE, (LO_Element**) &m_pLoImage, iEditOffset, lo_type, pLoElement); // Zero out the image_req field. We "know" that it is garbage at this point. pLoElement->lo_image.image_req = 0; // Make image URLs relative to document. bug 41009. EDT_ImageData *pData = GetImageData(); CEditBuffer *pBuffer = GetEditBuffer(); if( pData ) { XP_Bool bChanged = FALSE; if (pBuffer && !EDT_IS_NEW_DOCUMENT(pBuffer->m_pContext)){ if (edt_make_image_relative(pBuffer->GetBaseURL(),&pData->pSrc)) { bChanged = TRUE; } if (edt_make_image_relative(pBuffer->GetBaseURL(),&pData->pLowSrc)) { bChanged = TRUE; } } if (bChanged) { SetImageData(pData); } edt_FreeImageData(pData); } } void CEditImageElement::ResetLayoutElement( intn iEditOffset, LO_Element* pLoElement ){ ResetLayoutElementHelper((LO_Element**) &m_pLoImage, iEditOffset, pLoElement); } LO_Element* CEditImageElement::GetLayoutElement(){ return (LO_Element*)m_pLoImage; } void CEditImageElement::StreamOut( IStreamOut *pOut){ CEditLeafElement::StreamOut( pOut ); pOut->WriteInt( (int32)m_align ); pOut->WriteInt( m_bSizeWasGiven ); pOut->WriteInt( m_bSizeIsBogus ); pOut->WriteInt( m_iHeight ); pOut->WriteInt( m_iWidth ); pOut->WriteInt( m_bWidthPercent ); pOut->WriteInt( m_bHeightPercent ); // We need to make the Image Src and LowSrc Parameters absolute. // We do this by first converting the parameter string to and Edit // data structure and then making the paths absolute. EDT_ImageData *pData = GetImageData(); CEditBuffer *pBuffer = GetEditBuffer(); if( pData && pBuffer && pBuffer->GetBaseURL() ){ char *p; if( pData->pSrc ) { p = NET_MakeAbsoluteURL( pBuffer->GetBaseURL(), pData->pSrc ); if( p ){ XP_FREE( pData->pSrc ); pData->pSrc = p; } } if( pData->pLowSrc ) { p = NET_MakeAbsoluteURL( pBuffer->GetBaseURL(), pData->pLowSrc ); if( p ){ XP_FREE( pData->pLowSrc ); pData->pLowSrc = p; } } // Format the parameters for output. p = FormatParams( pData, TRUE ); pOut->WriteZString( p ); XP_FREE(p); edt_FreeImageData( pData ); } else { pOut->WriteZString( m_pParams ); } if( m_href != ED_LINK_ID_NONE ){ // Must make URLs absolute when copying CEditBuffer *pBuffer = GetEditBuffer(); char *pAbsolute = NULL; if( pBuffer ) pAbsolute = NET_MakeAbsoluteURL(LO_GetBaseURL(pBuffer->m_pContext), m_href->hrefStr); if( pAbsolute ) { pOut->WriteZString( pAbsolute ); XP_FREE(pAbsolute); } else pOut->WriteZString( m_href->hrefStr ); pOut->WriteZString( m_href->pExtra ); } else { pOut->WriteZString( 0 ); pOut->WriteZString( 0 ); } } EEditElementType CEditImageElement::GetElementType() { return eImageElement; } PA_Tag* CEditImageElement::TagOpen( int /* iEditOffset */ ){ char *pCur = 0; PA_Tag *pRetTag = 0; PA_Tag *pEndTag = 0; if( m_href != ED_LINK_ID_NONE ){ char *pBuf = PR_smprintf("HREF=%s %s>", edt_MakeParamString(m_href->hrefStr), (m_href->pExtra ? m_href->pExtra : "") ); edt_AddTag( pRetTag, pEndTag, P_ANCHOR, FALSE, pBuf ); free( pBuf ); } if( m_pParams ){ if( SizeIsKnown() ){ // Append the HEIGHT and WIDTH params, taking into account "%" mode pCur = XP_STRDUP(m_pParams); if( m_bHeightPercent ){ pCur = PR_sprintf_append( pCur, " HEIGHT=\"%ld%%\" ", (long)min(100,max(1,m_iHeight)) ); } else { pCur = PR_sprintf_append( pCur, " HEIGHT=\"%ld\" ", (long)m_iHeight ); } if( m_bWidthPercent ){ pCur = PR_sprintf_append( pCur, " WIDTH=\"%ld%%\" ", (long)min(100,max(1,m_iWidth)) ); } else { pCur = PR_sprintf_append( pCur, " WIDTH=\"%ld\" ", (long)m_iWidth ); } //CLM: Not sure if this is best behavior, but if size is // already set in HTML, should consider that to be "original"? // Maybe we should fill in here if we will end up getting same values in FinishedLoad //m_iOriginalHeight = m_iHeight; //m_iOriginalWidth = m_iWidth; } else { // We don't know the size, but if we don't give a size, // and if the image isn't known, we will block and // crash. So we put in a bogus size here, and take it // out in FinishedLoad if there's image data available. m_bSizeIsBogus = TRUE; pCur = PR_smprintf("%s HEIGHT=24 WIDTH=22", m_pParams ); } } // For editing, we don't support baseline, left or right alignment of images. if( m_align != ED_ALIGN_DEFAULT && m_align != ED_ALIGN_BASELINE && m_align != ED_ALIGN_LEFT && m_align != ED_ALIGN_RIGHT ){ pCur = PR_sprintf_append(pCur, "ALIGN=%s ", EDT_AlignString(m_align) ); } pCur = PR_sprintf_append(pCur, ">"); edt_AddTag( pRetTag, pEndTag, P_IMAGE, FALSE, pCur ); pEndTag->edit_element = this; if( m_href != ED_LINK_ID_NONE ){ edt_AddTag( pRetTag, pEndTag, P_ANCHOR, TRUE ); } free( pCur ); return pRetTag; } void CEditImageElement::PrintOpen( CPrintState *pPrintState ){ if( m_href != ED_LINK_ID_NONE ){ char *pS1 = ""; char *pS2 = ""; if( m_href->pExtra ){ pS1 = " "; pS2 = m_href->pExtra; } //added '\n' to HREF for visual purposes pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "\n", edt_MakeParamString(m_href->hrefStr), pS1, pS2 ); } // Use special versions of params which has BORDER removed if it's the default char* pParams = NULL; { EDT_ImageData* pData = GetImageData(); pParams = FormatParams(pData, TRUE); EDT_FreeImageData(pData); } if( SizeIsKnown() && ! m_bSizeIsBogus ){ pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "m_iCharPos += pPrintState->m_pOut->Printf( "HEIGHT=%ld%%", (long)m_iHeight ); } else { pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "HEIGHT=%ld", (long)m_iHeight ); } if( m_bWidthPercent ){ pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( " WIDTH=%ld%%", (long)m_iWidth ); } else { pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( " WIDTH=%ld", (long)m_iWidth ); } } else { pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "m_iCharPos += pPrintState->m_pOut->Printf( " ALIGN=%s", EDT_AlignString(m_align)); } pPrintState->m_pOut->Write( ">", 1 ); pPrintState->m_iCharPos++; if( m_href != ED_LINK_ID_NONE ){ pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "" ); } } static char *imageParams[] = { PARAM_ISMAP, // PARAM_USEMAP, Now in extra PARAM_SRC, PARAM_LOWSRC, PARAM_NAME, PARAM_ALT, PARAM_WIDTH, PARAM_HEIGHT, PARAM_ALIGN, PARAM_HSPACE, PARAM_VSPACE, PARAM_BORDER, PARAM_ALIGN, PARAM_NOSAVE, 0 }; EDT_ImageData* CEditImageElement::ParseParams( PA_Tag *pTag, int16 csid ){ PA_Block buff; EDT_ImageData* pData = edt_NewImageData(); buff = PA_FetchParamValue(pTag, PARAM_ISMAP, csid); if (buff != NULL){ pData->bIsMap = TRUE; PA_FREE(buff); } buff = PA_FetchParamValue(pTag, PARAM_NOSAVE, csid); if (buff != NULL){ pData->bNoSave = TRUE; PA_FREE(buff); } pData->pSrc = edt_FetchParamString( pTag, PARAM_SRC, csid ); pData->pLowSrc = edt_FetchParamString( pTag, PARAM_LOWSRC, csid ); pData->pName = edt_FetchParamString( pTag, PARAM_NAME, csid ); pData->pAlt = edt_FetchParamString( pTag, PARAM_ALT, csid ); // Fill in the ALT text string with the filename if none is supplied in HTML if( pData->pSrc && !pData->pAlt ) { char *pName = EDT_GetFilename(pData->pSrc, FALSE); if( pName ) { pData->pAlt = XP_STRDUP(pName); XP_FREE(pName); } } // Get width dimension from string and parse for "%" Default = 100% edt_FetchDimension( pTag, PARAM_WIDTH, &pData->iWidth, &pData->bWidthPercent, 0, FALSE, csid ); edt_FetchDimension( pTag, PARAM_HEIGHT, &pData->iHeight, &pData->bHeightPercent, 0, FALSE, csid ); m_bSizeWasGiven = pData->iWidth != 0 && pData->iHeight != 0; pData->align = edt_FetchParamAlignment( pTag, ED_ALIGN_BASELINE, FALSE, csid ); pData->iHSpace = edt_FetchParamInt( pTag, PARAM_HSPACE, 0, csid ); pData->iVSpace = edt_FetchParamInt( pTag, PARAM_VSPACE, 0, csid ); pData->iBorder = edt_FetchParamInt( pTag, PARAM_BORDER, -1, csid ); pData->pHREFData = 0; pData->pExtra = edt_FetchParamExtras( pTag, imageParams, csid ); return pData; } EDT_ImageData* CEditImageElement::GetImageData(){ EDT_ImageData *pRet; // LTNOTE: hackola time. We don't want TagOpen to return multiple tags // in this use, so we prevent it by faking out the href ID and restoring // it when we are done. Not real maintainable. ED_LinkId saveHref = m_href; m_href = ED_LINK_ID_NONE; PA_Tag* pTag = TagOpen(0); m_href = saveHref; pRet = ParseParams( pTag, GetWinCSID() ); if ( m_bSizeIsBogus ) { pRet->iWidth = 0; pRet->bWidthPercent = FALSE; pRet->iHeight = 0; pRet->bHeightPercent = FALSE; } //CLM: We stored this when image was first loaded: int originalWidth = 0; int originalHeight = 0; if ( m_pLoImage ) { IL_GetNaturalDimensions( m_pLoImage->image_req, &originalWidth, &originalHeight); } pRet->iOriginalWidth = originalWidth; pRet->iOriginalHeight = originalHeight; pRet->bWidthPercent = m_bWidthPercent; pRet->bHeightPercent = m_bHeightPercent; if( m_href != ED_LINK_ID_NONE ) pRet->pHREFData = m_href->GetData(); // We need to set the link border if there's an HREF or USEMAP // Unfortunately, we don't explicitly handle USEMAP any more // so we must fish for it in the tag. We don't worry about win_csid // (last param) since we just want to know if string exists char *pUsemap = edt_FetchParamString(pTag, PARAM_USEMAP, CS_FE_ASCII ); if( pUsemap || m_href != ED_LINK_ID_NONE ) { // The only way to consistently show the "default" 2-pixel border // is to set it to 2 if it was missing in the tag params if( pRet->iBorder == -1 ) pRet->iBorder = 2; //Don't use GetDefaultBorder - doesn't consider USEMAP } pRet->align = m_align; PA_FreeTag( pTag ); XP_FREEIF(pUsemap); return pRet; } // border is weird. If we are in a link the border is default 2, if // we are not in a link, border is default 0. In order not to foist this // weirdness on the user, we are always explicit on our image borders. // This isn't correct - it doesn't consider if there's a USEMAP // 9/29/98: This was used by FEs, but now that we always set a border value // instead of trying to maintain the -1 that means no BORDER param is written, // this isn't called, so let's not worry about USEMAP int32 CEditImageElement::GetDefaultBorder(){ return ( m_href != ED_LINK_ID_NONE ) ? 2 : 0; } XP_Bool CEditImageElement::GetLOElementAndOffset( ElementOffset iEditOffset, XP_Bool /*bEditStickyAfter*/ , LO_Element*& pRetElement, int& pLayoutOffset ){ pLayoutOffset = 0; pRetElement = (LO_Element*)m_pLoImage; pLayoutOffset = (int) iEditOffset; return TRUE; } void CEditImageElement::SetImageData( EDT_ImageData *pData ){ char* pCur = FormatParams(pData, FALSE); if( m_pParams ){ XP_FREE( m_pParams ); } m_pParams = pCur; m_align = pData->align; if (pData->iHeight != 0 && pData->iWidth != 0 ) { m_bSizeWasGiven = TRUE; m_bSizeIsBogus = FALSE; m_bHeightPercent = pData->bHeightPercent; m_bWidthPercent = pData->bWidthPercent; } else { m_bSizeWasGiven = FALSE; //CLM: We were given Width or Height = 0 // so we will get REAL dimensions // Clearing these willl allow us to retrieve actual dimensions // on an image that had WIDTH and/or HEIGHT values on 1st load m_bHeightPercent = 0; m_bWidthPercent = 0; } m_iHeight = pData->iHeight; m_iWidth = pData->iWidth; } char* CEditImageElement::FormatParams(EDT_ImageData* pData, XP_Bool bForPrinting){ char *pCur = 0; if( pData->bIsMap ){ pCur = PR_sprintf_append( pCur, "ISMAP " ); } /* USEMAP now in pExtra if( pData->pUseMap ){ pCur = PR_sprintf_append( pCur, "USEMAP=%s ", edt_MakeParamString(pData->pUseMap) ); } */ if( pData->pSrc ){ pCur = PR_sprintf_append( pCur, "SRC=%s ", edt_MakeParamString( pData->pSrc ) ); } if( pData->pLowSrc ){ pCur = PR_sprintf_append( pCur, "LOWSRC=%s ", edt_MakeParamString( pData->pLowSrc ) ); } if( pData->pName ){ pCur = PR_sprintf_append( pCur, "NAME=%s ", edt_MakeParamString( pData->pName ) ); } if( pData->pAlt ){ pCur = PR_sprintf_append( pCur, "ALT=%s ", edt_MakeParamString( pData->pAlt ) ); } if( pData->iHSpace ){ pCur = PR_sprintf_append( pCur, "HSPACE=%ld ", (long)pData->iHSpace ); } if( pData->iVSpace ){ pCur = PR_sprintf_append( pCur, "VSPACE=%ld ", (long)pData->iVSpace ); } if( pData->bNoSave ){ pCur = PR_sprintf_append( pCur, "NOSAVE " ); } // border is weird. If we are in a link the border is default 2, if // we are not in a link, border is default 0. When printing, we only write out a // border if it's different than the default. // // Other times, we always write it out because we want it to stay the same even if the // default border size changes. Whew! { if( !bForPrinting || ( pData->iBorder >=0 )){ pCur = PR_sprintf_append( pCur, "BORDER=%ld ", (long)pData->iBorder ); } } if( pData->pExtra != 0 ){ pCur = PR_sprintf_append( pCur, "%s ", pData->pExtra); } return pCur; } void CEditImageElement::FinishedLoad( CEditBuffer *pBuffer ){ if( m_pLoImage ){ if ( ! m_bSizeWasGiven || m_bSizeIsBogus ) { // The user didn't specify a size. Look at what we got. m_iHeight = pBuffer->m_pContext->convertPixY * m_pLoImage->height; m_iWidth = pBuffer->m_pContext->convertPixX * m_pLoImage->width; // This fixes lots of image size problems (like bug 148158), // but we will ALWAYS write out the size, even if not in original HTML m_bSizeIsBogus = FALSE; } } } void CEditImageElement::SetHREF( ED_LinkId id ){ if( m_href != ED_LINK_ID_NONE ){ m_href->Release(); } m_href = id; if( id != ED_LINK_ID_NONE ){ id->AddRef(); } } XP_Bool CEditImageElement::SizeIsKnown() { return m_iHeight > 0; } void CEditImageElement::MaskData( EDT_CharacterData*& pData ){ if( pData == 0 ){ pData = GetCharacterData(); return; } // An image can have an HREF text format ED_TextFormat tf = ( m_href == ED_LINK_ID_NONE ) ? 0 : TF_HREF; ED_TextFormat differences = pData->values ^ tf; if( differences ){ pData->mask &= ~differences; } if( (pData->mask & pData->values & tf & TF_HREF) && (pData->linkId != m_href ) ){ pData->mask &= ~TF_HREF; } } EDT_CharacterData* CEditImageElement::GetCharacterData(){ EDT_CharacterData *pData = EDT_NewCharacterData(); // We are only interested in HREF? // pData->mask = TF_HREF; pData->mask = -1; if( m_href != ED_LINK_ID_NONE ){ pData->pHREFData = m_href->GetData(); pData->values = TF_HREF; } pData->linkId = m_href; return pData; } //----------------------------------------------------------------------------- // CEditHorizRuleElement //----------------------------------------------------------------------------- CEditHorizRuleElement::CEditHorizRuleElement( CEditElement *pParent, PA_Tag* pTag, int16 /*csid*/ ): CEditLeafElement( pParent, P_HRULE ), m_pLoHorizRule(0) { if( pTag ){ char *locked_buff; PA_LOCK(locked_buff, char *, pTag->data ); if( locked_buff ){ SetTagData( locked_buff ); } PA_UNLOCK(pTag->data); } } CEditHorizRuleElement::CEditHorizRuleElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer): CEditLeafElement(pStreamIn, pBuffer), m_pLoHorizRule(0) { } CEditHorizRuleElement::~CEditHorizRuleElement(){ DisconnectLayoutElements((LO_Element*) m_pLoHorizRule); } LO_Element* CEditHorizRuleElement::GetLayoutElement(){ return (LO_Element*)m_pLoHorizRule; } void CEditHorizRuleElement::SetLayoutElement( intn iEditOffset, intn lo_type, LO_Element* pLoElement ){ SetLayoutElementHelper(LO_HRULE, (LO_Element**) &m_pLoHorizRule, iEditOffset, lo_type, pLoElement); } void CEditHorizRuleElement::ResetLayoutElement( intn iEditOffset, LO_Element* pLoElement ){ ResetLayoutElementHelper((LO_Element**) &m_pLoHorizRule, iEditOffset, pLoElement); } void CEditHorizRuleElement::StreamOut( IStreamOut *pOut ){ CEditLeafElement::StreamOut( pOut ); } EEditElementType CEditHorizRuleElement::GetElementType() { return eHorizRuleElement; } XP_Bool CEditHorizRuleElement::GetLOElementAndOffset( ElementOffset iEditOffset, XP_Bool /* bEditStickyAfter */, LO_Element*& pRetElement, int& pLayoutOffset ){ pRetElement = (LO_Element*)m_pLoHorizRule; pLayoutOffset = (int) iEditOffset; return TRUE; } // Property Getting and Setting Stuff. EDT_HorizRuleData* CEditHorizRuleElement::NewData(){ EDT_HorizRuleData *pData = XP_NEW( EDT_HorizRuleData ); if( pData == 0 ){ // throw(); return pData; } pData->align = ED_ALIGN_CENTER; pData->size = DEFAULT_HR_THICKNESS; pData->bNoShade = 0; pData->iWidth = 100; pData->bWidthPercent = TRUE; pData->pExtra = 0; return pData; } void CEditHorizRuleElement::FreeData( EDT_HorizRuleData *pData ){ if( pData->pExtra ) XP_FREE( pData->pExtra ); XP_FREE( pData ); } void CEditHorizRuleElement::SetData( EDT_HorizRuleData *pData ){ char *pNew = 0; if( pData->align == ED_ALIGN_RIGHT || pData->align == ED_ALIGN_LEFT ){ pNew = PR_sprintf_append( pNew, "ALIGN=%s ", EDT_AlignString(pData->align) ); } if( pData->size != DEFAULT_HR_THICKNESS ){ pNew = PR_sprintf_append( pNew, "SIZE=%ld ", (long)pData->size ); } if( pData->bNoShade ){ pNew = PR_sprintf_append( pNew, "NOSHADE " ); } if( pData->iWidth ){ if( pData->bWidthPercent ){ pNew = PR_sprintf_append( pNew, "WIDTH=\"%ld%%\" ", (long)min(100,max(1,pData->iWidth)) ); } else { pNew = PR_sprintf_append( pNew, "WIDTH=\"%ld\" ", (long)pData->iWidth ); } } if( pData->pExtra){ pNew = PR_sprintf_append( pNew, "%s ", pData->pExtra ); } pNew = PR_sprintf_append( pNew, ">" ); SetTagData( pNew ); free(pNew); } EDT_HorizRuleData* CEditHorizRuleElement::GetData( ){ EDT_HorizRuleData *pRet; PA_Tag* pTag = TagOpen(0); pRet = ParseParams( pTag, GetWinCSID() ); PA_FreeTag( pTag ); return pRet; } static char *hruleParams[] = { PARAM_ALIGN, PARAM_NOSHADE, PARAM_WIDTH, PARAM_SIZE, 0 }; EDT_HorizRuleData* CEditHorizRuleElement::ParseParams( PA_Tag *pTag, int16 csid ){ EDT_HorizRuleData *pData = NewData(); ED_Alignment align; align = edt_FetchParamAlignment( pTag, ED_ALIGN_CENTER, FALSE, csid ); if( align == ED_ALIGN_RIGHT || align == ED_ALIGN_LEFT ){ pData->align = align; } // Get width dimension from string and parse for "%" Default = 100% edt_FetchDimension( pTag, PARAM_WIDTH, &pData->iWidth, &pData->bWidthPercent, 100, TRUE, csid ); pData->bNoShade = edt_FetchParamBoolExist( pTag, PARAM_NOSHADE, csid ); pData->size = edt_FetchParamInt( pTag, PARAM_SIZE, DEFAULT_HR_THICKNESS, csid ); pData->pExtra = edt_FetchParamExtras( pTag, hruleParams, csid ); return pData; } //----------------------------------------------------------------------------- // CEditIconElement //----------------------------------------------------------------------------- static char *ppIconTags[] = { "align=absbotom border=0 src=\"internal-edit-named-anchor\" alt=\"%s\">", "align=absbotom border=0 src=\"internal-edit-form-element\">", "src=\"internal-edit-unsupported-tag\" alt=\"<%s\"", "src=\"internal-edit-unsupported-end-tag\" alt=\"", "align=absbotom border=0 src=\"internal-edit-plugin\">", 0 }; CEditIconElement::CEditIconElement( CEditElement *pParent, int32 iconTag, PA_Tag* pTag, int16 /*csid*/ ) : CEditLeafElement( pParent, P_IMAGE ), m_originalTagType( pTag ? pTag->type : P_UNKNOWN ), m_iconTag( iconTag ), m_bEndTag( pTag ? pTag->is_end : 0 ), m_pSpoofData(0), m_pLoIcon(0), m_piSaveIndices(NULL) { if( pTag ){ char *locked_buff; PA_LOCK(locked_buff, char *, pTag->data ); if( locked_buff ){ char *p = locked_buff; if( m_originalTagType != P_ANCHOR && m_originalTagType != P_UNKNOWN ){ while( *p == ' ' ) p++; char *pSpace = " "; if( *p == '>' ) pSpace = ""; char *pTagData = PR_smprintf("%s%s%s", EDT_TagString(m_originalTagType), pSpace, p ); SetTagData( pTagData ); SetTagData( pTag, pTagData ); m_originalTagType = P_UNKNOWN; free( pTagData ); } else { SetTagData( locked_buff ); } } PA_UNLOCK(pTag->data); SetSpoofData( pTag ); } } CEditIconElement::~CEditIconElement(){ DisconnectLayoutElements((LO_Element*) m_pLoIcon); if( m_pSpoofData ){ free( m_pSpoofData ); } XP_FREEIF(m_piSaveIndices); } CEditIconElement::CEditIconElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer): CEditLeafElement(pStreamIn, pBuffer), m_pLoIcon(0), m_piSaveIndices(NULL) { m_originalTagType = (TagType) pStreamIn->ReadInt(); m_iconTag = pStreamIn->ReadInt(); m_bEndTag = pStreamIn->ReadInt() ? TRUE : FALSE; m_pSpoofData = pStreamIn->ReadZString(); } void CEditIconElement::SetLayoutElement( intn iEditOffset, intn lo_type, LO_Element* pLoElement ){ SetLayoutElementHelper(LO_IMAGE, (LO_Element**) &m_pLoIcon, iEditOffset, lo_type, pLoElement); } void CEditIconElement::ResetLayoutElement( intn iEditOffset, LO_Element* pLoElement ){ ResetLayoutElementHelper((LO_Element**) &m_pLoIcon, iEditOffset, pLoElement); } void CEditIconElement::StreamOut( IStreamOut *pOut ){ CEditLeafElement::StreamOut( pOut ); pOut->WriteInt( m_originalTagType ); pOut->WriteInt( m_iconTag ); pOut->WriteInt( m_bEndTag ); pOut->WriteZString( m_pSpoofData ); } XP_Bool CEditIconElement::GetLOElementAndOffset( ElementOffset iEditOffset, XP_Bool /* bEditStickyAfter */, LO_Element*& pRetElement, int& pLayoutOffset ){ pLayoutOffset = 0; pRetElement = (LO_Element*)m_pLoIcon; pLayoutOffset = iEditOffset; return TRUE; } PA_Tag* CEditIconElement::TagOpen( int /* iEditOffset */ ){ PA_Tag *pTag = XP_NEW( PA_Tag ); XP_BZERO( pTag, sizeof( PA_Tag ) ); SetTagData( pTag, m_pSpoofData ); return pTag; } #define IS_WHITE(p) ((p) == ' ' || (p) == '\r' || (p) == '\n') PRIVATE XP_Bool IsScriptText2(char* pData, char* pName){ char *p = pData; while( *p && IS_WHITE(*p) ){ p++; } if ( !*p || *p != '<' ) { return FALSE; } p++; char *n = pName; while ( *p && * n && ! IS_WHITE(*p) && XP_TO_LOWER(*p) == XP_TO_LOWER(*n) ) { p++; n++; } if ( *n != 0 || (*p != '>' && ! IS_WHITE(*p)) ) { // We didn't match the tag! return FALSE; } // Go to the end of the string. while ( *p ) { p++; } p--; // Back up to the last '>'. Nothing but whitespace to last '>' while ( p > pData && (*p != '>' && IS_WHITE(*p) ) ) { --p; } if ( p <= pData || *p != '>' ) { return FALSE; } // Back-match to last '<' while ( *p && *p != '<' && p > pData ) { p--; } if ( p <= pData || *p != '<' ) { return FALSE; } if ( *++p != '/' ) { return FALSE; } if ( strcasestr(++p, pName) == NULL ) { return FALSE; } p += XP_STRLEN(pName); if ( *p != '>' && ! IS_WHITE(*p) ) { return FALSE; } return TRUE; } PRIVATE XP_Bool IsScriptText(char* pData) { // Return TRUE if pData is , or , or ... return IsScriptText2(pData, "SCRIPT") || IsScriptText2(pData, "STYLE") || IsScriptText2(pData, "SERVER"); } // // Do some heuristic validation to make sure // ED_TagValidateResult CEditIconElement::ValidateTag( char *pData, XP_Bool bNoBrackets ){ if ( IsScriptText(pData) ) { return ED_TAG_OK; } char *p = pData; while( *p && IS_WHITE(*p) ){ p++; } if( !bNoBrackets ){ if( *p != '<' ){ return ED_TAG_UNOPENED; } else { p++; } if( *p == '/' ){ p++; } if( IS_WHITE(*p) ){ return ED_TAG_TAGNAME_EXPECTED; } } // Is this a comment? XP_Bool isComment = p[0] == '!' && p[1] == '-' && p[2] == '-'; if ( isComment ) { char* start = p; while( *p ){ p++; } if ( ! bNoBrackets ) { --p; } while ( IS_WHITE(*p) && p > start ) { p--; } XP_Bool endComment = (p >= start + 6) && p[-2] == '-' && p[-1] == '-'; if ( ! endComment ) { return ED_TAG_PREMATURE_CLOSE; // ToDo: Use a specific error message } } else { // look for unterminated strings. while( *p && *p != '>' ){ if( *p == '"' || *p == '\'' ){ char quote = *p++; while( *p && *p != quote ){ p++; } if( *p == 0 ){ return ED_TAG_UNTERMINATED_STRING; } } p++; } } if( bNoBrackets ){ if( *p == 0 ){ return ED_TAG_OK; } else { XP_ASSERT( *p == '>' ); return ED_TAG_PREMATURE_CLOSE; } } else if( *p == '>' ){ p++; while( IS_WHITE( *p ) ){ p++; } if( *p != 0 ){ return ED_TAG_PREMATURE_CLOSE; } else { return ED_TAG_OK; } } else { XP_ASSERT( *p == 0 ); return ED_TAG_UNCLOSED; } } // // Restore the original tag type so we save the right tag type when we output // we print. // void CEditIconElement::PrintOpen( CPrintState *pPrintState ){ if( m_originalTagType != P_UNKNOWN ){ TagType tSave = GetType(); SetType( m_originalTagType ); CEditLeafElement::PrintOpen( pPrintState ); SetType( tSave ); } else { if( m_bEndTag ){ pPrintState->m_iCharPos += pPrintState->m_pOut->Printf( "m_iCharPos += pPrintState->m_pOut->Printf( "<%s", GetTagData()); } } } void CEditIconElement::PrintEnd( CPrintState *pPrintState ){ if( m_originalTagType != P_UNKNOWN ){ TagType tSave = GetType(); SetType( m_originalTagType ); CEditLeafElement::PrintEnd( pPrintState ); SetType( tSave ); } } char* CEditIconElement::GetData(){ int iLen = XP_STRLEN( GetTagData() ); char *p = (char*)XP_ALLOC( iLen+3 ); char *pRet = p; *p++ = '<'; if( m_bEndTag ){ *p++ = '/'; } XP_STRCPY( p, GetTagData() ); return pRet; } PRIVATE void edt_addToStringLists(char **list1,char *start1,char *end1, char **list2,char *start2,char *end2, int index) { int len1 = end1 - start1; int len2 = end2 - start2; // Don't allow empty strings. if ((len1 > 0) && (len2 > 0)) { // space for trailing NULL. list1[index] = (char *)XP_ALLOC(len1 + 1); list2[index] = (char *)XP_ALLOC(len2 + 1); if (!list1[index] || !list2[index]) { XP_ASSERT(0); return; } // copy strings. XP_MEMCPY(list1[index],start1,len1); list1[index][len1] = '\0'; XP_MEMCPY(list2[index],start2,len2); list2[index][len2] = '\0'; } } int CEditIconElement::ParseLocalData(char ***mimeTypes,char ***URLs) { int retVal = 0; *mimeTypes = NULL; *URLs = NULL; // Don't want the version with the spoofData, so call ancestor. PA_Tag *pTag = CEditElement::TagOpen(0); if (pTag) { // Don't use edt_FetchParamString because it strips out interior // white space. PA_Block buff = PA_FetchParamValue(pTag,PARAM_LOCALDATA,GetWinCSID()); if (buff) { char *pLocalData; PA_LOCK(pLocalData,char *,buff); // maxFiles = 1+number of '+' characters in pLocalData, // gives upper bound on size of returned lists. int maxFiles = 1; char *p = pLocalData; while (*p && (p = XP_STRCHR(p,'+')) != NULL) { maxFiles++; p++; // move past found '+' } // Create lists. *mimeTypes = (char **)XP_ALLOC(maxFiles * sizeof(char *)); if (!*mimeTypes) { return 0; } *URLs = (char **)XP_ALLOC(maxFiles * sizeof(char *)); if (!*URLs) { XP_FREEIF(*mimeTypes); // Will zero *mimeTypes. return 0; } // reset p. p = pLocalData; char *mimeStart=NULL, *mimeEnd=NULL, *URLStart=NULL, *URLEnd=NULL; // State machine to read in string int state = BeforeMIME; while(*p) { switch (state) { case BeforeMIME: if (!XP_IS_SPACE(*p)) { state = InMIME; mimeStart = p; } break; case InMIME: if (XP_IS_SPACE(*p)) { mimeEnd = p; state = BeforeURL; } break; case BeforeURL: if (!XP_IS_SPACE(*p)) { URLStart = p; state = InURL; } break; case InURL: if (XP_IS_SPACE(*p)) { URLEnd = p; XP_ASSERT(retVal < maxFiles); edt_addToStringLists(*mimeTypes,mimeStart,mimeEnd, *URLs,URLStart,URLEnd, retVal); retVal++; state = AfterURL; } if (*p == '+') { URLEnd = p; XP_ASSERT(retVal < maxFiles); edt_addToStringLists(*mimeTypes,mimeStart,mimeEnd, *URLs,URLStart,URLEnd, retVal); retVal++; state = BeforeMIME; } break; case AfterURL: if (*p == '+') { state = BeforeMIME; } break; default: XP_ASSERT(0); break; } // Move forward. p++; } // Close out final mime/URL pair. if (state == InURL) { URLEnd = p; XP_ASSERT(retVal < maxFiles); edt_addToStringLists(*mimeTypes,mimeStart,mimeEnd, *URLs,URLStart,URLEnd, retVal); retVal++; } PA_UNLOCK(buff); PA_FREE(buff); } PA_FreeTag(pTag); } return retVal; } void CEditIconElement::FreeLocalDataLists(char **mimeTypes,char **URLs,int count) { for (int n = 0; n < count; n++) { XP_FREEIF(mimeTypes[n]); XP_FREEIF(URLs[n]); } XP_FREEIF(mimeTypes); XP_FREEIF(URLs); } void CEditIconElement::ReplaceParamValues(char *pOld,char *pNew) { // Not changing anything. if (!XP_STRCMP(pOld,pNew)) return; // Illegal. if (!pOld || !pNew || pOld[0] == '=' || pOld[0] == '\"') return; char *pTagData = GetTagData(); if (!pTagData) return; int state = OutsideValue; char *last = pTagData; // last part copied to pNewTagData. char *scan = pTagData; // Scans ahead of last. char *pNewTagData = NULL; int pOldLen = XP_STRLEN(pOld); StrAllocCat(pNewTagData,"<"); while (*scan) { XP_ASSERT(scan); switch (state) { case OutsideValue: if (*scan == '=') { state = BeforeValue; } break; case InsideValue: if (XP_IS_SPACE(*scan)) { // end of non-quoted value. state = OutsideValue; } break; case InsideValueQuote: if (*scan == '\"') { // closing quote. state = OutsideValue; } break; case BeforeValue: if (*scan == '\"') { state = InsideValueQuote; } else if (!XP_IS_SPACE(*scan)) { state = InsideValue; } // else still BeforeValue break; default: XP_ASSERT(0); } if (state == InsideValue || state == InsideValueQuote) { // Found an instance. if (!XP_STRNCMP(scan,pOld,pOldLen)) { // flush last part. if (last != scan) { char store = *scan; *scan = '\0'; StrAllocCat(pNewTagData,last); *scan = store; } StrAllocCat(pNewTagData,pNew); scan += pOldLen; // skip past prev string. last = scan; scan--; // To counteract following scan++. } } scan++; } // flush last part. if (last != scan) { StrAllocCat(pNewTagData,last); last = scan; } SetData(pNewTagData); XP_FREEIF(pNewTagData); } PRIVATE XP_Bool IsHTMLCommentTag(char* string) { return string && string[0] == '!' && string[1] == '-' && string[2] == '-'; } void CEditIconElement::SetSpoofData( PA_Tag* pTag ){ if( m_iconTag == EDT_ICON_UNSUPPORTED_TAG || m_iconTag == EDT_ICON_UNSUPPORTED_END_TAG ){ char *pTagString; PA_LOCK(pTagString, char *, pTag->data ); char *pData = PR_smprintf( ppIconTags[m_iconTag], edt_QuoteString( pTagString )); XP_Bool bDimensionFound = FALSE; XP_Bool bComment = m_originalTagType == P_UNKNOWN && IsHTMLCommentTag(pTagString); if ( ! bComment ) { int32 height; int32 width; XP_Bool bPercent; edt_FetchDimension( pTag, PARAM_HEIGHT, &height, &bPercent, -1, TRUE, GetWinCSID() ); if( height != -1 ){ pData = PR_sprintf_append( pData, " HEIGHT=%d%s", height, (bPercent ? "%" : "" ) ); bDimensionFound = TRUE; } edt_FetchDimension( pTag, PARAM_WIDTH, &width, &bPercent, -1, TRUE, GetWinCSID() ); if( width != -1 ){ pData = PR_sprintf_append( pData, " WIDTH=%d%s", width, (bPercent ? "%" : "" ) ); bDimensionFound = TRUE; } } if( bDimensionFound ){ pData = PR_sprintf_append(pData, " BORDER=2>"); } else { pData = PR_sprintf_append(pData, " BORDER=0 ALIGN=ABSBOTTOM>"); } if( m_pSpoofData ){ free( m_pSpoofData ); } m_pSpoofData = pData; PA_UNLOCK(pTag->data); } else { m_pSpoofData = XP_STRDUP( ppIconTags[m_iconTag] ); } } void CEditIconElement::SetSpoofData( char* pData ){ char *pNewData = PR_smprintf( ppIconTags[m_iconTag], edt_QuoteString( pData )); if( m_pSpoofData ){ XP_FREE( m_pSpoofData ); } m_pSpoofData = pNewData; } void CEditIconElement::SetData( char *pData ){ // Since validater skips white space, so should SetData while( *pData && IS_WHITE(*pData) ){ pData++; } // If you assert here, you aren't validating your tag. if( pData[0] != '<' ) { XP_ASSERT(FALSE); return; } pData++; if( *pData == '/' ){ pData++; m_bEndTag = TRUE; } SetTagData( pData ); // Build up a tag that we can fetch parameter strings from. PA_Tag *pTag = XP_NEW( PA_Tag ); XP_BZERO( pTag, sizeof( PA_Tag ) ); SetTagData( pTag, pData ); pTag->is_end = m_bEndTag; SetSpoofData( pTag ); PA_FreeTag( pTag ); } // // We need to convert a tag handed to us by the parser into a tag we can // display in the editor. In order to do this, we 'morph' the tag by swaping // the contents with desired tag. We need an additional tag for swap space. // All this is done to insure that the memory location of 'pTag' doesn't // change. Perhaps this routine should be implemented at the CEditElement // layer. // void CEditIconElement::MorphTag( PA_Tag *pTag ){ PA_Tag *pNewTag = TagOpen(0); PA_Tag tempTag; tempTag = *pTag; *pTag = *pNewTag; *pNewTag = tempTag; PA_FreeTag( pNewTag ); } XP_Bool CEditIconElement::IsUnknownHTML(){ return m_iconTag == EDT_ICON_UNSUPPORTED_TAG || m_iconTag == EDT_ICON_UNSUPPORTED_END_TAG; } XP_Bool CEditIconElement::IsComment(){ return IsUnknownHTML() && IsHTMLCommentTag(GetTagData()); } XP_Bool CEditIconElement::IsComment(char* prefix){ if ( ! IsComment() ) return FALSE; char* pData = GetTagData(); if ( ! prefix || ! pData ) return FALSE; int32 prefixLen = XP_STRLEN(prefix); int32 dataLen = XP_STRLEN(pData); // Skip past "!--" if ( dataLen <= 3 ) return FALSE; dataLen -= 3; pData += 3; // Skip whitespace while ( dataLen > 0 && *pData == ' ' ){ pData++; dataLen--; } if ( prefixLen > dataLen ) return FALSE; if ( XP_STRNCMP(prefix, pData, prefixLen) == 0 ){ return TRUE; } return FALSE; } void CEditIconElement::SetSize(XP_Bool bWidthPercent, int32 iWidth, XP_Bool bHeightPercent, int32 iHeight){ // Do NOT use TagOpen -- this gets the "m_pSpoofData" junk PA_Tag *pTag = XP_NEW( PA_Tag ); XP_BZERO( pTag, sizeof( PA_Tag ) ); // This string has initial "<" SetTagData( pTag, GetData() ); if( pTag ){ char *pWidth = 0; char *pHeight = 0; if( iWidth > 0 ){ pWidth = PR_sprintf_append( pWidth, "%ld%s", (long)iWidth, bWidthPercent ? "%%" : "" ); if( pWidth ){ edt_ReplaceParamValue( pTag, "WIDTH", pWidth, GetWinCSID()); } } if( iHeight > 0){ pHeight = PR_sprintf_append( pHeight, "%ld%s", (long)iHeight, bHeightPercent ? "%%" : "" ); if( pHeight ){ edt_ReplaceParamValue( pTag, "HEIGHT", pHeight, GetWinCSID()); } } if( pWidth || pHeight ){ SetData((char*)pTag->data); if( pWidth ) XP_FREE(pWidth); if( pHeight ) XP_FREE(pHeight); } PA_FREE(pTag); } } //----------------------------------------------------------------------------- // CEditTargetElement //----------------------------------------------------------------------------- CEditTargetElement::CEditTargetElement(CEditElement *pParent, PA_Tag* pTag, int16 csid) : CEditIconElement( pParent, EDT_ICON_NAMED_ANCHOR, pTag ) { m_originalTagType = P_ANCHOR; // To get the spoof tag set up correctly EDT_TargetData* pData = GetData(csid); SetData( pData ); FreeTargetData(pData); } CEditTargetElement::CEditTargetElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer) : CEditIconElement( pStreamIn, pBuffer ) { } void CEditTargetElement::StreamOut( IStreamOut *pOut){ CEditIconElement::StreamOut( pOut ); } PA_Tag* CEditTargetElement::TagOpen( int iEditOffset ){ // We need to create the actual anchor tag set. return CEditIconElement::TagOpen( iEditOffset ); } void CEditTargetElement::SetName( char* pName, int16 csid ){ if ( pName ) { EDT_TargetData* pData = GetData(csid); if ( pData ) { if ( pData->pName ) { XP_FREE(pData->pName); } pData->pName = XP_STRDUP(pName); } SetData(pData); FreeTargetData(pData); } } void CEditTargetElement::SetData( EDT_TargetData *pData ){ char *pNew = 0; if ( pData->pName) { pNew = PR_sprintf_append( pNew, "NAME=%s ", edt_MakeParamString(pData->pName)); } if( pData->pExtra ){ pNew = PR_sprintf_append( pNew, "%s ", pData->pExtra ); } if( pNew ){ pNew = PR_sprintf_append( pNew, ">" ); } SetTagData( pNew ); if ( pNew ) { free(pNew); } // Set the spoof data for the icon. CEditIconElement::SetSpoofData( pData->pName ? pData->pName : "" ); } char* CEditTargetElement::GetName(){ char* pName = 0; EDT_TargetData* pData = GetData(); if (pData && pData->pName) { pName = XP_STRDUP(pData->pName); } FreeTargetData(pData); return pName; } EDT_TargetData* CEditTargetElement::GetData(){ return GetData(GetWinCSID()); } EDT_TargetData* CEditTargetElement::GetData(int16 csid){ EDT_TargetData *pRet; // Get the actual tag data, not the faked up stuff at the Icon layer. PA_Tag* pTag = CEditLeafElement::TagOpen(0); pRet = ParseParams( pTag, csid ); PA_FreeTag( pTag ); return pRet; } static char *targetParams[] = { PARAM_NAME, 0 }; EDT_TargetData* CEditTargetElement::ParseParams( PA_Tag *pTag, int16 csid ){ EDT_TargetData* pData = NewTargetData(); pData->pName = edt_FetchParamString( pTag, PARAM_NAME, csid ); pData->pExtra = edt_FetchParamExtras( pTag, targetParams, csid ); return pData; } EDT_TargetData* CEditTargetElement::NewTargetData(){ EDT_TargetData* pData = (EDT_TargetData*) XP_ALLOC(sizeof(EDT_TargetData)); if ( pData ) { pData->pName = 0; pData->pExtra = 0; } return pData; } void CEditTargetElement::FreeTargetData(EDT_TargetData* pData){ if ( pData ) { if ( pData->pName ) { XP_FREE(pData->pName); } if ( pData->pExtra ) { XP_FREE(pData->pExtra); } XP_FREE(pData); } } //----------------------------------------------------------------------------- // CEditBreakElement //----------------------------------------------------------------------------- CEditBreakElement::CEditBreakElement( CEditElement *pParent, PA_Tag* pTag, int16 /*csid*/ ): CEditLeafElement( pParent, P_LINEBREAK ), m_pLoLinefeed(0) { if( pTag ){ char *locked_buff; PA_LOCK(locked_buff, char *, pTag->data ); if( locked_buff ){ SetTagData( locked_buff ); } PA_UNLOCK(pTag->data); } } CEditBreakElement::CEditBreakElement(IStreamIn *pStreamIn, CEditBuffer *pBuffer): CEditLeafElement(pStreamIn, pBuffer), m_pLoLinefeed(0) { } CEditBreakElement::~CEditBreakElement(){ DisconnectLayoutElements((LO_Element*) m_pLoLinefeed); } void CEditBreakElement::StreamOut( IStreamOut *pOut){ CEditLeafElement::StreamOut( pOut ); } EEditElementType CEditBreakElement::GetElementType(){ return eBreakElement; } void CEditBreakElement::SetLayoutElement( intn iEditOffset, intn lo_type, LO_Element* pLoElement ){ SetLayoutElementHelper(LO_LINEFEED, (LO_Element**) &m_pLoLinefeed, iEditOffset, lo_type, pLoElement); } void CEditBreakElement::ResetLayoutElement( intn iEditOffset, LO_Element* pLoElement ){ ResetLayoutElementHelper((LO_Element**) &m_pLoLinefeed, iEditOffset, pLoElement); } LO_Element* CEditBreakElement::GetLayoutElement(){ return (LO_Element*)m_pLoLinefeed; } XP_Bool CEditBreakElement::GetLOElementAndOffset( ElementOffset iEditOffset, XP_Bool /* bStickyAfter */, LO_Element*& pRetElement, int& pLayoutOffset ){ pLayoutOffset = 0; pRetElement = (LO_Element*)m_pLoLinefeed; pLayoutOffset = iEditOffset; return TRUE; } // // if we are within PREFORMAT (or the like) breaks are returns. // void CEditBreakElement::PrintOpen( CPrintState *ps ){ CEditElement *pPrev = PreviousLeafInContainer(); while( pPrev && pPrev->IsA(P_LINEBREAK) ){ pPrev = pPrev->PreviousLeafInContainer(); } CEditElement *pNext = LeafInContainerAfter(); while( pNext && pNext->IsA(P_LINEBREAK) ){ pNext = pNext->LeafInContainerAfter(); } #ifdef USE_SCRIPT XP_Bool bScriptBefore = pPrev && pPrev->IsA(P_TEXT) && (pPrev->Text()->m_tf & (TF_SERVER|TF_SCRIPT|TF_STYLE)); XP_Bool bScriptAfter = pNext && pNext->IsA(P_TEXT) && (pNext->Text()->m_tf & (TF_SERVER|TF_SCRIPT|TF_STYLE)); if ( !bScriptBefore && bScriptAfter ){ // Don't print
s before