/* -*- 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( "%s>", 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( "%s>", 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
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
"); 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( "