/* This file is part of the KDE project Copyright (C) 1998, 1999, 2000 Reginald Stadlbauer Copyright (C) 2001-2006 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KWTextFrameSet.h" #include "KWTableFrameSet.h" #include "KWDocument.h" #include "KWView.h" #include "KWViewMode.h" #include "KWCanvas.h" #include "KWAnchor.h" #include "KWCommand.h" #include "KWFormulaFrameSet.h" #include "KWBgSpellCheck.h" #include "KWordTextFrameSetIface.h" #include "KWordTextFrameSetEditIface.h" #include "KWordFootNoteFrameSetIface.h" #include "KWordFrameSetIface.h" #include "KWLoadingInfo.h" #include "KWInsertTOCCommand.h" #include "KWMailMergeDataBase.h" #include "KoTextBookmark.h" #include "KWVariable.h" #include "KWOasisSaver.h" #include "KWFrameList.h" #include "KWPageManager.h" #include "KWPage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define DEBUG_MARGINS //#define DEBUG_FORMATVERTICALLY //#define DEBUG_FORMATS //#define DEBUG_FORMAT_MORE //#define DEBUG_VIEWAREA //#define DEBUG_CURSOR //#define DEBUG_DTI //#define DEBUG_ITD /** * KWord's text formatter. * It derives from KoTextFormatter and simply forwards formatVertically to KWTextFrameSet, * since only KWTextFrameSet knows about page-breaking, overlapping frames etc. */ class KWTextFormatter : public KoTextFormatter { public: KWTextFormatter( KWTextFrameSet *textfs ) : m_textfs( textfs ) {} virtual ~KWTextFormatter() {} virtual int formatVertically( KoTextDocument*, KoTextParag* parag ) { return m_textfs->formatVertically( parag, parag->rect() ); } virtual void postFormat( KoTextParag* parag ) { m_textfs->fixParagWidth( static_cast( parag ) ); } private: KWTextFrameSet *m_textfs; }; KWTextFrameSet::KWTextFrameSet( KWDocument *_doc, const TQString & name ) : KWFrameSet( _doc ) { //kdDebug() << "KWTextFrameSet::KWTextFrameSet " << this << endl; if ( name.isEmpty() ) m_name = _doc->generateFramesetName( i18n( "Text Frameset %1" ) ); else m_name = name; TQObject::setName( m_name.utf8() ); // store name in the TQObject, for DCOP users init(); } KWTextFrameSet::KWTextFrameSet( KWDocument* doc, const TQDomElement& tag, KoOasisContext& /*context*/ ) : KWFrameSet( doc ) { m_name = tag.attributeNS( KoXmlNS::draw, "name", TQString() ); if ( doc->frameSetByName( m_name ) ) // already exists! m_name = doc->generateFramesetName( m_name + " %1" ); init(); // Note that we don't call loadOasis here. The caller wants to do it, // to get the frame it returns. } // protected constructor for testing purposes; does not do an init. KWTextFrameSet::KWTextFrameSet( const TQString &name ) : KWFrameSet(0) { m_name = name; TQObject::setName( m_name.utf8() ); // store name in the TQObject, for DCOP users m_currentViewMode = 0L; m_currentDrawnFrame = 0L; m_lastTextDocHeight = 0; m_textobj = 0; } void KWTextFrameSet::init() { m_currentViewMode = 0L; m_currentDrawnFrame = 0L; m_lastTextDocHeight = 0; // Create the text document to set in the text object KWTextDocument* textdoc = new KWTextDocument( this, new KoTextFormatCollection( m_doc->defaultFont(), TQColor(), m_doc->globalLanguage(), m_doc->globalHyphenation() ), new KWTextFormatter( this ) ); textdoc->setFlow( this ); textdoc->setPageBreakEnabled( true ); // get verticalBreak to be called if ( m_doc->tabStopValue() != -1 ) textdoc->setTabStops( m_doc->ptToLayoutUnitPixX( m_doc->tabStopValue() )); m_textobj = new KoTextObject( textdoc, m_doc->styleCollection()->findStyle( "Standard" ), this, (m_name+"-textobj").utf8() ); m_doc->backSpeller()->registerNewTextObject( m_textobj ); connect( m_textobj, TQ_SIGNAL( availableHeightNeeded() ), TQ_SLOT( slotAvailableHeightNeeded() ) ); connect( m_textobj, TQ_SIGNAL( afterFormatting( int, KoTextParag*, bool* ) ), TQ_SLOT( slotAfterFormatting( int, KoTextParag*, bool* ) ) ); //connect( m_textobj, TQ_SIGNAL( formattingFirstParag() ), // TQ_SLOT( slotFormattingFirstParag() ) ); //connect( m_textobj, TQ_SIGNAL( chapterParagraphFormatted( KoTextParag * ) ), // TQ_SLOT( slotChapterParagraphFormatted( KoTextParag * ) ) ); connect( m_textobj, TQ_SIGNAL( newCommand( KCommand * ) ), TQ_SLOT( slotNewCommand( KCommand * ) ) ); connect( m_textobj, TQ_SIGNAL( repaintChanged( KoTextObject* ) ), TQ_SLOT( slotRepaintChanged() ) ); connect( m_textobj, TQ_SIGNAL( paragraphDeleted( KoTextParag*) ), TQ_SLOT( slotParagraphDeleted(KoTextParag*) )); connect( m_textobj, TQ_SIGNAL( paragraphCreated( KoTextParag*) ), TQ_SLOT( slotParagraphCreated(KoTextParag*) )); connect( m_textobj, TQ_SIGNAL( paragraphModified( KoTextParag*, int, int, int) ), TQ_SLOT( slotParagraphModified(KoTextParag*, int, int, int) )); } void KWTextFrameSet::slotParagraphModified(KoTextParag* _parag, int /*KoTextParag::ParagModifyType*/ _type, int start, int length) { kWordDocument()->paragraphModified(_parag, _type, start, length); } void KWTextFrameSet::slotParagraphCreated(KoTextParag* /*_parag*/) { //todo } void KWTextFrameSet::slotParagraphDeleted(KoTextParag*_parag) { kWordDocument()->paragraphDeleted( _parag, this); } KWordFrameSetIface* KWTextFrameSet::dcopObject() { if ( !m_dcop ) m_dcop = new KWordTextFrameSetIface( this ); return m_dcop; } KWFrameSetEdit * KWTextFrameSet::createFrameSetEdit( KWCanvas * canvas ) { return new KWTextFrameSetEdit( this, canvas ); } KoTextDocument * KWTextFrameSet::textDocument() const { return m_textobj->textDocument(); } KWTextDocument * KWTextFrameSet::kwTextDocument() const { return static_cast(m_textobj->textDocument()); } void KWTextFrameSet::slotAvailableHeightNeeded() { Q_ASSERT( isVisible() ); kdDebug() << "KWTextFrameSet::slotAvailableHeightNeeded " << name() << endl; updateFrames( 0 ); // only do the available-height determination } KWFrame * KWTextFrameSet::documentToInternal( const KoPoint &dPoint, TQPoint &iPoint ) const { #ifdef DEBUG_DTI kdDebug() << "KWTextFrameSet::documentToInternal dPoint:" << dPoint.x() << "," << dPoint.y() << endl; #endif if ( !m_doc->layoutViewMode()->hasFrames() ) { // text viewmode iPoint = TQPoint( m_doc->ptToLayoutUnitPixX( dPoint.x() ), m_doc->ptToLayoutUnitPixY( dPoint.y() ) ); return m_frames.getFirst(); } // Find the frame that contains dPoint. To go fast, we look them up by page number. int pageNum = m_doc->pageManager()->pageNumber(dPoint); TQPtrListIterator frameIt( framesInPage( pageNum ) ); for ( ; frameIt.current(); ++frameIt ) { KWFrame *theFrame = frameIt.current(); if ( theFrame->contains( dPoint ) ) { iPoint.setX( m_doc->ptToLayoutUnitPixX( dPoint.x() - theFrame->innerRect().x() ) ); iPoint.setY( m_doc->ptToLayoutUnitPixY( dPoint.y() - theFrame->innerRect().y() + theFrame->internalY() ) ); #ifdef DEBUG_DTI kdDebug() << "documentToInternal: returning " << iPoint.x() << "," << iPoint.y() << " internalY=" << theFrame->internalY() << " because frame=" << theFrame << " contains dPoint:" << dPoint.x() << "," << dPoint.y() << endl; #endif return theFrame; } #ifdef DEBUG_DTI //else // kdDebug() << "DTI: " << frameRect // << " doesn't contain nPoint:" << nPoint.x() << "," << nPoint.y() << endl; #endif } // Not found. This means the mouse isn't over any frame, in the page pageNum. iPoint = m_doc->ptToLayoutUnitPix( dPoint ); // bah return 0; } KWFrame * KWTextFrameSet::documentToInternalMouseSelection( const KoPoint &dPoint, TQPoint &iPoint, RelativePosition& relPos, KWViewMode *viewMode ) const { #ifdef DEBUG_DTI kdDebug() << "KWTextFrameSet::documentToInternalMouseSelection dPoint:" << dPoint.x() << "," << dPoint.y() << endl; #endif if ( !viewMode->hasFrames() ) { // text viewmode relPos = InsideFrame; iPoint = TQPoint( m_doc->ptToLayoutUnitPixX( dPoint.x() ), m_doc->ptToLayoutUnitPixY( dPoint.y() ) ); return m_frames.getFirst(); } // Find the frame that contains dPoint. To go fast, we look them up by page number. int pageNum = m_doc->pageManager()->pageNumber(dPoint); TQPtrListIterator frameIt( framesInPage( pageNum ) ); for ( ; frameIt.current(); ++frameIt ) { KWFrame *theFrame = frameIt.current(); if ( theFrame->contains( dPoint ) ) { iPoint.setX( m_doc->ptToLayoutUnitPixX( dPoint.x() - theFrame->innerRect().x() ) ); iPoint.setY( m_doc->ptToLayoutUnitPixY( dPoint.y() - theFrame->innerRect().y() + theFrame->internalY() ) ); #ifdef DEBUG_DTI kdDebug() << "documentToInternal: returning InsideFrame " << iPoint.x() << "," << iPoint.y() << " internalY=" << theFrame->internalY() << " because frame=" << theFrame << " contains dPoint:" << dPoint.x() << "," << dPoint.y() << endl; #endif relPos = InsideFrame; return theFrame; } } frameIt.toFirst(); for ( ; frameIt.current(); ++frameIt ) { KWFrame *theFrame = frameIt.current(); KoRect openLeftRect( theFrame->innerRect() ); openLeftRect.setLeft( theFrame->paddingLeft() ); #ifdef DEBUG_DTI kdDebug() << "documentToInternal: openLeftRect=" << openLeftRect << endl; #endif if ( openLeftRect.contains( dPoint ) ) { // We are at the left of this frame (and not in any other frame of this frameset) iPoint.setX( m_doc->ptToLayoutUnitPixX(theFrame->innerRect().left() )); iPoint.setY( m_doc->ptToLayoutUnitPixY( dPoint.y() - theFrame->top() + theFrame->internalY() ) ); #ifdef DEBUG_DTI kdDebug() << "documentToInternal: returning LeftOfFrame " << iPoint.x() << "," << iPoint.y() << " internalY=" << theFrame->internalY() << " because openLeftRect=" << openLeftRect << " contains dPoint:" << dPoint.x() << "," << dPoint.y() << endl; #endif relPos = LeftOfFrame; return theFrame; } KoRect openTopRect( KoPoint( 0, 0 ), theFrame->innerRect().bottomRight() ); #ifdef DEBUG_DTI kdDebug() << "documentToInternal: openTopRect=" << openTopRect << endl; #endif if ( openTopRect.contains( dPoint ) ) { // We are at the top of this frame (...) iPoint.setX( m_doc->ptToLayoutUnitPixX( dPoint.x() - theFrame->innerRect().left() ) ); iPoint.setY( m_doc->ptToLayoutUnitPixY( theFrame->internalY() ) ); #ifdef DEBUG_DTI kdDebug() << "documentToInternal: returning " << iPoint.x() << "," << iPoint.y() << " internalY=" << theFrame->internalY() << " because openTopRect=" << openTopRect << " contains dPoint:" << dPoint.x() << "," << dPoint.y() << endl; #endif relPos = TopOfFrame; return theFrame; } } // Not found. This means we are under (or at the right of), the frames in pageNum. // In that case, go for the first frame in the next page. if ( pageNum + 1 >= (int)m_framesInPage.size() + m_firstPage ) { // Under last frame of last page! KWFrame *theFrame = m_frames.getLast(); iPoint.setX( m_doc->ptToLayoutUnitPixX( theFrame->innerWidth() ) ); iPoint.setY( m_doc->ptToLayoutUnitPixY( theFrame->innerHeight() ) ); #ifdef DEBUG_DTI kdDebug() << "documentToInternal: returning AtEnd " << iPoint.x() << "," << iPoint.y() << " because we are under all frames of the last page" << endl; #endif relPos = AtEnd; return theFrame; } else { TQPtrListIterator frameIt( framesInPage( pageNum + 1 ) ); if ( frameIt.current() ) { // There is a frame on the next page KWFrame *theFrame = frameIt.current(); KoRect openTopRect( theFrame->innerRect() ); openTopRect.setTop( 0 ); if ( openTopRect.contains( dPoint ) ) // We are at the top of this frame iPoint.setX( m_doc->ptToLayoutUnitPixX( dPoint.x() - theFrame->left() ) ); else iPoint.setX( 0 ); // We are, hmm, on the left or right of the frames iPoint.setY( m_doc->ptToLayoutUnitPixY( theFrame->internalY() ) ); #ifdef DEBUG_DTI kdDebug() << "documentToInternal: returning TopOfFrame " << iPoint.x() << "," << iPoint.y() << " because we are under all frames of page " << pageNum << endl; #endif relPos = TopOfFrame; return theFrame; } // else there is a gap (no frames on that page, but on some other further down) // This case isn't handled (and should be VERY rare I think) } iPoint = m_doc->ptToLayoutUnitPix( dPoint ); // bah #ifdef DEBUG_DTI kdDebug() << "documentToInternal: returning not found for " << iPoint.x() << "," << iPoint.y() << endl; #endif return 0; } KWFrame * KWTextFrameSet::internalToDocumentWithHint( const TQPoint &iPoint, KoPoint &dPoint, const KoPoint &hintDPoint ) const { #ifdef DEBUG_ITD kdDebug() << "KWTextFrameSet::internalToDocumentWithHint hintDPoint: " << hintDPoint.x() << "," << hintDPoint.y() << endl; #endif if ( !m_doc->layoutViewMode()->hasFrames() ) { // text viewmode dPoint = m_doc->layoutUnitPtToPt( m_doc->pixelToPt( iPoint ) ); return m_frames.getFirst(); } KWFrame *lastFrame = 0L; TQPtrListIterator frameIt( frameIterator() ); for ( ; frameIt.current(); ++frameIt ) { KWFrame *theFrame = frameIt.current(); TQRect r( 0, m_doc->ptToLayoutUnitPixY( theFrame->internalY() ), m_doc->ptToLayoutUnitPixX( theFrame->innerWidth() )+1, m_doc->ptToLayoutUnitPixY( theFrame->innerHeight() )+1 ); #ifdef DEBUG_ITD kdDebug() << "ITD: r=" << r << " iPoint=" << iPoint.x() << "," << iPoint.y() << endl; #endif // r is the frame in qrt coords if ( r.contains( iPoint ) ) // both r and p are in layout units (aka internal) { dPoint = internalToDocumentKnowingFrame( iPoint, theFrame ); #ifdef DEBUG_ITD kdDebug() << "copy: " << theFrame->isCopy() << " hintDPoint.y()=" << hintDPoint.y() << " dPoint.y()=" << dPoint.y() << endl; #endif // First test: No "hintDPoint" specified, go for the first match // Second test: hintDPoint specified, check if we are far enough if ( hintDPoint.isNull() || hintDPoint.y() <= dPoint.y() ) return theFrame; // Remember that this frame matched, in case we find no further frame that matches lastFrame = theFrame; } else if ( lastFrame ) { return lastFrame; } } // This happens when the parag is on a not-yet-created page (formatMore will notice afterwards) // So it doesn't matter much what happens here, we'll redo it anyway. #ifdef DEBUG_ITD kdDebug(32002) << "KWTextFrameSet::internalToDocumentWithHint " << iPoint.x() << "," << iPoint.y() << " not in any frame of " << (void*)this << endl; #endif dPoint = m_doc->layoutUnitPtToPt( m_doc->pixelToPt( iPoint ) ); // bah return 0L; } // relPoint is in relative coordinates (pt) KoPoint KWTextFrameSet::internalToDocumentKnowingFrame( const KoPoint &relPoint, KWFrame* theFrame ) const { // It's ok to have theFrame == 0 in the text viewmode, but not in other modes if ( m_doc->layoutViewMode()->hasFrames() ) Q_ASSERT( theFrame ); if ( theFrame ) return KoPoint( relPoint.x() + theFrame->innerRect().x(), relPoint.y() - theFrame->internalY() + theFrame->innerRect().y() ); else return relPoint; } KoPoint KWTextFrameSet::internalToDocumentKnowingFrame( const TQPoint &iPoint, KWFrame* theFrame ) const { // Convert LU to relative coordinates (pt), then call the real internalToDocumentKnowingFrame(). return internalToDocumentKnowingFrame( m_doc->layoutUnitPtToPt( m_doc->pixelToPt( iPoint ) ), theFrame ); } TQPoint KWTextFrameSet::moveToPage( int currentPgNum, short int direction ) const { if ( !isVisible() || m_frames.isEmpty() ) return TQPoint(); //kdDebug() << "KWTextFrameSet::moveToPage currentPgNum=" << currentPgNum << " direction=" << direction << endl; int num = currentPgNum + direction; int pages = m_doc->pageCount(); for ( ; num >= 0 && num < pages ; num += direction ) { //kdDebug() << "KWTextFrameSet::moveToPage num=" << num << " pages=" << pages << endl; // Find the first frame on page num if ( num < m_firstPage || num >= (int)m_framesInPage.size() + m_firstPage ) continue; // No frame on that page //kdDebug() << "KWTextFrameSet::moveToPage ok for first frame in page " << num << endl; TQPtrListIterator frameIt( framesInPage( num ) ); return TQPoint( 0, m_doc->ptToLayoutUnitPixY( frameIt.current()->internalY() ) + 2 ); // found, ok. } // Not found. Go to top of first frame or bottom of last frame, depending on direction if ( direction < 0 ) return TQPoint( 0, m_doc->ptToLayoutUnitPixY( m_frames.getFirst()->internalY() ) + 2 ); else { KWFrame * theFrame = m_frames.getLast(); return TQPoint( 0, m_doc->ptToLayoutUnitPixY( theFrame->internalY() + theFrame->innerHeight() ) ); } } void KWTextFrameSet::drawContents( TQPainter *p, const TQRect & crect, const TQColorGroup &cg, bool onlyChanged, bool resetChanged, KWFrameSetEdit *edit, KWViewMode *viewMode, KWFrameViewManager *fvm) { m_currentViewMode = viewMode; KWFrameSet::drawContents( p, crect, cg, onlyChanged, resetChanged, edit, viewMode, fvm ); // Main textframeset: draw the footnote line if there are footnotes if ( isMainFrameset() && viewMode->hasFrames() ) { // We stored the info "there's a footnote in this page" in the frame[s] // of the maintextframeset for that page. Usually one, but could be more // if there are columns. However we want to draw the line only once, so we // do it here and not in drawFrame (we'd have problems with cliprect anyway). if ( m_doc->footNoteSeparatorLineWidth() ==0.0) return; int pages = m_doc->pageCount(); KWPage *page = m_doc->pageManager()->page(m_doc->pageManager()->startPage()); double left = page->leftMargin(); double pageWidth = page->width() - page->rightMargin() - left; double width = pageWidth * m_doc->footNoteSeparatorLineLength() / 100.0; int numColumns = m_doc->numColumns(); for ( int pageNum = 0; pageNum < pages; pageNum++ ) { //if ( viewMode->isPageVisible( pageNum ) ) { uint frameNum = pageNum * numColumns /*+ col 0 here*/; if ( frameNum < frameCount() ) // not true on the "endnotes-only" page { KWFrame* frame = this->frame( frameNum ); // ## or use framesInPage ? //kdDebug() << " Footnote line: page " << pageNum << " found frame " << frameNum << " drawFootNoteLine=" << frame->drawFootNoteLine() << endl; if ( frame->drawFootNoteLine() ) { double y = frame->bottomLeft().y() + m_doc->headerFooterInfo().ptFootNoteBodySpacing / 2; KoRect rect( left, y, width, 0 ); // this rect is flat switch( m_doc->footNoteSeparatorLinePosition()) { case SLP_LEFT: break; case SLP_CENTERED: rect = KoRect( pageWidth/2.0+left-width/2.0, y,width,0); break; case SLP_RIGHT: rect = KoRect( pageWidth+left-width, y,width,0); break; } TQRect flatRect = viewMode->normalToView( m_doc->zoomRect( rect ) ); //kdDebug() << " KWTextFrameSet::drawContents rect=" << rect << " zoomed:" << flatRect << endl; flatRect.setBottom( flatRect.top() + 1 ); // #!@!@!& TQRect.... if ( flatRect.intersects( crect ) ) { p->save(); TQPen pen( KoTextFormat::defaultTextColor( p ), // always in default fg color (and black when printing) KoBorder::zoomWidthY( m_doc->footNoteSeparatorLineWidth(), m_doc, 1 ) ); // penwidth = zoomIt( 2 pt ) switch( m_doc->footNoteSeparatorLineType()) { case SLT_SOLID: pen.setStyle( TQt::SolidLine ); break; case SLT_DASH: pen.setStyle( TQt::DashLine ); break; case SLT_DOT: pen.setStyle( TQt::DotLine ); break; case SLT_DASH_DOT: pen.setStyle( TQt::DashDotLine ); break; case SLT_DASH_DOT_DOT: pen.setStyle( TQt::DashDotDotLine ); break; } p->setPen( pen ); p->drawLine( flatRect.left(), flatRect.top(), flatRect.right(), flatRect.top() ); //kdDebug() << " drawLine( " << flatRect.left() << ", " << flatRect.top() << ", " << flatRect.right() << ", " << flatRect.top() << endl; p->restore(); } } } } } } } void KWTextFrameSet::drawFrame( KWFrame *theFrame, TQPainter *painter, const TQRect &fcrect, const TQRect &crect, const TQPoint& translationOffset, KWFrame *settingsFrame, const TQColorGroup &cg, bool onlyChanged, bool resetChanged, KWFrameSetEdit *edit, KWViewMode *viewMode, bool drawUnderlyingFrames ) { // Detect if text frame needs transparency painting, to save time if it's using SolidPattern // In theory this code should be in kwFrameSet, but currently only text frames obey m_backgroundColor. if ( theFrame ) { drawUnderlyingFrames &= theFrame->isTransparent(); } KWFrameSet::drawFrame( theFrame, painter, fcrect, crect, translationOffset, settingsFrame, cg, onlyChanged, resetChanged, edit, viewMode, drawUnderlyingFrames ); } void KWTextFrameSet::drawFrameContents( KWFrame *theFrame, TQPainter *painter, const TQRect &r, const TQColorGroup &cg, bool onlyChanged, bool resetChanged, KWFrameSetEdit *edit, KWViewMode *viewMode ) { Q_ASSERT( r.isValid() ); // In this method the painter is translated to the frame's coordinate system // (in the first frame (0,0) will be its topleft, in the second frame it will be (0,internalY) etc. //kdDebug(32001) << "KWTextFrameSet::drawFrameContents " << name() << "(frame " << frameFromPtr( theFrame ) << ") crect(r)=" << r << " onlyChanged=" << onlyChanged << endl; m_currentDrawnFrame = theFrame; if ( theFrame ) // 0L in the text viewmode { // Update variables for each frame (e.g. for page-number) // If more than KWPgNumVariable need this functionality, create an intermediary base class TQPtrListIterator cit( textDocument()->allCustomItems() ); for ( ; cit.current() ; ++cit ) { KWPgNumVariable * var = dynamic_cast( cit.current() ); if ( var && !var->isDeleted() ) { TQSize oldSize( var->width, var->height ); const int pageNumberOffset = kWordDocument()->variableCollection()->variableSetting()->startingPageNumber() - 1; switch ( var->subType() ) { case KWPgNumVariable::VST_PGNUM_CURRENT: //kdDebug() << "KWTextFrameSet::drawFrame updating pgnum variable to " << theFrame->pageNumber() // << " and invalidating parag " << var->paragraph() << endl; var->setPgNum( theFrame->pageNumber() + pageNumberOffset ); break; case KWPgNumVariable::VST_CURRENT_SECTION: var->setSectionTitle( kWordDocument()->sectionTitle( theFrame->pageNumber() ) ); break; case KWPgNumVariable::VST_PGNUM_PREVIOUS: var->setPgNum( TQMAX(theFrame->pageNumber()-1,0) + pageNumberOffset ); break; case KWPgNumVariable::VST_PGNUM_NEXT: var->setPgNum( theFrame->pageNumber() + 1 + pageNumberOffset ); break; } var->resize(); TQSize newSize( var->width, var->height ); if ( oldSize != newSize ) var->paragraph()->invalidate( 0 ); // size has changed -> need reformatting ! var->paragraph()->setChanged( true ); } } } KoTextCursor * cursor = edit ? (dynamic_cast(edit) ? static_cast(edit)->cursor() : 0) : 0; uint drawingFlags = 0; if ( viewMode->drawSelections() ) drawingFlags |= KoTextDocument::DrawSelections; if ( !viewMode->drawFrameBackground() ) drawingFlags |= KoTextDocument::TransparentBackground; if ( m_doc->backgroundSpellCheckEnabled() ) drawingFlags |= KoTextDocument::DrawMisspelledLine; if ( m_doc->viewFormattingChars() ) drawingFlags |= KoTextDocument::DrawFormattingChars; //kdDebug(32001) << "KWTextFrameSet::drawFrame calling drawWYSIWYG. cg base color:" << cg.brush( TQColorGroup::Base) << endl; KoTextParag * lastFormatted = textDocument()->drawWYSIWYG( painter, r.x(), r.y(), r.width(), r.height(), cg, kWordDocument(), onlyChanged, false, cursor, resetChanged, drawingFlags ); // The last paragraph of this frame might have a bit in the next frame too. // In that case, and if we're only drawing changed paragraphs, (and resetting changed), // we have to set changed to true again, to draw the bottom of the parag in the next frame. if ( onlyChanged && resetChanged ) { // Finding the "last parag of the frame" is a bit tricky. // It's usually the one before lastFormatted, except if it's actually lastParag :} [see KoTextDocument::draw] KoTextParag * lastDrawn = lastFormatted->prev(); if ( lastFormatted == textDocument()->lastParag() && ( !lastDrawn || m_doc->layoutUnitToPixelY( lastDrawn->rect().bottom() ) < r.bottom() ) ) lastDrawn = lastFormatted; //kdDebug(32002) << "KWTextFrameSet::drawFrame drawn. onlyChanged=" << onlyChanged << " resetChanged=" << resetChanged << " lastDrawn=" << lastDrawn->paragId() << " lastDrawn's bottom:" << lastDrawn->rect().bottom() << " r.bottom=" << r.bottom() << endl; if ( lastDrawn && m_doc->layoutUnitToPixelY( lastDrawn->rect().bottom() ) > r.bottom() ) { //kdDebug(32002) << "KWTextFrameSet::drawFrame setting lastDrawn " << lastDrawn->paragId() << " to changed" << endl; lastDrawn->setChanged( true ); } } // NOTE: TQTextView sets m_lastFormatted to lastFormatted here // But when scrolling up, this causes to reformat a lot of stuff for nothing. // And updateViewArea takes care of formatting things before we even arrive here. // Blank area under the very last paragraph - TQRT draws it up to textdoc->height, // we have to draw it from there up to the bottom of the last frame. if ( ( !lastFormatted || lastFormatted == textDocument()->lastParag() ) && viewMode->drawFrameBackground() ) { // This is drawing code, so we convert everything to pixels int docHeight = textDocument()->lastParag()->pixelRect(m_doc).bottom() + 1; //TQRect frameRect = m_doc->zoomRect( (theFrame->innerRect()) ); TQSize availSize = viewMode->availableSizeForText( this ); TQRect blank( 0, docHeight, availSize.width(), availSize.height() /*+ frameRect.height() ?? */ - docHeight ); //kdDebug(32002) << this << " Blank area: " << blank << endl; painter->fillRect( blank, cg.brush( TQColorGroup::Base ) ); // for debugging :) //painter->setPen( TQPen(TQt::blue, 1, DashLine) ); painter->drawRect( blank ); } m_currentDrawnFrame = 0L; } void KWTextFrameSet::drawCursor( TQPainter *p, KoTextCursor *cursor, bool cursorVisible, KWCanvas *canvas, KWFrame *theFrame ) { // This redraws the paragraph where the cursor is - with a small clip region around the cursor m_currentViewMode = canvas->viewMode(); bool hasFrames = m_currentViewMode->hasFrames(); m_currentDrawnFrame = theFrame; TQRect normalFrameRect; if (hasFrames) normalFrameRect = m_doc->zoomRect( theFrame->innerRect() ); else normalFrameRect = TQRect( TQPoint(0, 0), m_currentViewMode->contentsSize() ); KoTextParag* parag = cursor->parag(); TQPoint topLeft = parag->rect().topLeft(); // in TQRT coords int lineY; // Cursor height, in pixels int cursorHeight = m_doc->layoutUnitToPixelY( topLeft.y(), parag->lineHeightOfChar( cursor->index(), 0, &lineY ) ); TQPoint iPoint( topLeft.x() + cursor->x(), topLeft.y() + lineY ); #ifdef DEBUG_CURSOR kdDebug() << "KWTextFrameSet::drawCursor topLeft=" << topLeft.x() << "," << topLeft.y() << " x=" << cursor->x() << " y=" << lineY << endl; kdDebug() << "KWTextFrameSet::drawCursor iPoint=" << iPoint.x() << "," << iPoint.y() << " cursorHeight=" << cursorHeight << endl; #endif KoPoint dPoint; KoPoint hintDPoint = theFrame ? theFrame->innerRect().topLeft() : KoPoint(); if ( internalToDocumentWithHint( iPoint, dPoint, hintDPoint ) ) { #ifdef DEBUG_CURSOR kdDebug() << " dPoint(doc, pts)=" << dPoint.x() << "," << dPoint.y() << endl; TQPoint debugPt = m_doc->zoomPoint( dPoint ); kdDebug() << " zoomed dPoint(doc, pixels)=" << debugPt.x() << "," << debugPt.y() << endl; #endif TQPoint vPoint = m_currentViewMode->normalToView( m_doc->zoomPoint( dPoint ) ); // from doc to view contents #ifdef DEBUG_CURSOR kdDebug() << " vPoint(view, pixels)=" << vPoint.x() << "," << vPoint.y() << endl; #endif // from now on, iPoint will be in pixels iPoint = m_doc->layoutUnitToPixel( iPoint ); //int xadj = parag->at( cursor->index() )->pixelxadj; #ifdef DEBUG_CURSOR //kdDebug() << " iPoint in pixels : " << iPoint.x() << "," << iPoint.y() << " will add xadj=" << xadj << endl; #endif //iPoint.rx() += xadj; //vPoint.rx() += xadj; // very small clipping around the cursor TQRect clip( vPoint.x() - 5, vPoint.y(), 10, cursorHeight ); #ifdef DEBUG_CURSOR kdDebug() << " clip(view, before intersect)=" << clip << endl; #endif TQRect viewFrameRect = m_currentViewMode->normalToView( normalFrameRect ); clip &= viewFrameRect; // clip to frame #ifdef DEBUG_CURSOR kdDebug() << "KWTextFrameSet::drawCursor normalFrameRect=" << normalFrameRect << " clip(view, after intersect)=" << clip << endl; #endif TQRegion reg; if ( hasFrames ) { reg = frameClipRegion( p, theFrame, clip, m_currentViewMode ); if ( !isFloating() ) // problem with multiparent inline frames reg &= p->xForm( viewFrameRect ); } if ( !hasFrames || !reg.isEmpty() ) { #ifdef DEBUG_CURSOR // for debug only! //p->fillRect( clip, TQBrush( TQt::red, TQBrush::Dense3Pattern ) ); #endif p->save(); TQColorGroup cg = TQApplication::palette().active(); if ( hasFrames ) { p->setClipRegion( reg ); // translate to qrt coords - after setting the clip region ! // see internalToDocumentWithHint int translationX = viewFrameRect.left(); int translationY = viewFrameRect.top() - m_doc->zoomItY( theFrame->internalY() ); #ifdef DEBUG_CURSOR kdDebug() << " translating Y by viewFrameRect.top()-internalY in pixelY= " << viewFrameRect.top() << "-" << m_doc->zoomItY( theFrame->internalY() ) << "=" << viewFrameRect.top() - m_doc->zoomItY( theFrame->internalY() ) << endl; #endif p->translate( translationX, translationY ); p->setBrushOrigin( p->brushOrigin().x() + translationX, p->brushOrigin().y() + translationY ); // The settings come from this frame KWFrame * settings = settingsFrame( theFrame ); TQBrush bgBrush( settings->backgroundColor() ); bgBrush.setColor( KWDocument::resolveBgColor( bgBrush.color(), p ) ); cg.setBrush( TQColorGroup::Base, bgBrush ); // color of cursor, the inverse of the frame background TQColor background = bgBrush.color(); cg.setColor(TQColorGroup::Text, TQColor( 255 - background.red(), 255 - background.green(), 255 - background.blue()) ); } else if(dynamic_cast(m_currentViewMode) != 0) p->translate( KWViewModeText::OFFSET, 0 ); TQPixmap *pix = 0; uint drawingFlags = KoTextDocument::DrawSelections; if ( m_doc->backgroundSpellCheckEnabled() ) drawingFlags |= KoTextDocument::DrawMisspelledLine; if ( m_doc->viewFormattingChars() ) drawingFlags |= KoTextDocument::DrawFormattingChars; // To force the drawing to happen: bool wasChanged = parag->hasChanged(); int oldLineChanged = parag->lineChanged(); int line; // line number parag->lineStartOfChar( cursor->index(), 0, &line ); parag->setChanged( false ); // not all changed, only from a given line parag->setLineChanged( line ); textDocument()->drawParagWYSIWYG( p, parag, TQMAX(0, iPoint.x() - 5), // negative values create problems iPoint.y(), clip.width(), clip.height(), pix, cg, m_doc, // TODO view's zoom handler cursorVisible, cursor, FALSE /*resetChanged*/, drawingFlags ); if ( wasChanged ) // Maybe we have more changes to draw, than those in the small cliprect parag->setLineChanged( oldLineChanged ); // -1 = all else parag->setChanged( false ); p->restore(); //XIM Position TQPoint ximPoint = vPoint; canvas->setXimPosition( ximPoint.x(), ximPoint.y(), 0, cursorHeight - parag->lineSpacing( line ) ); } } m_currentDrawnFrame = 0L; } void KWTextFrameSet::layout() { invalidate(); // Get the thing going though, repainting doesn't call formatMore m_textobj->formatMore( 2 ); } void KWTextFrameSet::invalidate() { //kdDebug() << "KWTextFrameSet::invalidate " << name() << endl; m_textobj->setLastFormattedParag( textDocument()->firstParag() ); textDocument()->invalidate(); // lazy layout, real update follows upon next repaint } void KWTextFrameSet::slotRepaintChanged() { emit repaintChanged( this ); } int KWTextFrameSet::paragraphs() { int paragraphs = 0; KoTextParag * parag = textDocument()->firstParag(); for ( ; parag ; parag = parag->next() ) paragraphs++; return paragraphs; } int KWTextFrameSet::paragraphsSelected() { int paragraphs = 0; KoTextParag *parag = textDocument()->firstParag(); for ( ; parag ; parag = parag->next() ) { if ( parag->hasSelection( KoTextDocument::Standard ) ) paragraphs++; } return paragraphs; } bool KWTextFrameSet::statistics( TQProgressDialog *progress, ulong & charsWithSpace, ulong & charsWithoutSpace, ulong & words, ulong & sentences, ulong & syllables, ulong & lines, bool selected ) { return m_textobj->statistics( progress, charsWithSpace, charsWithoutSpace, words, sentences, syllables, lines, selected ); } // Only interested in the body textframeset, not in header/footer #define kdDebugBody(area) if ( frameSetInfo() == FI_BODY ) kdDebug(area) TQValueList KWTextFrameSet::framesFromTo( int y1, int y2 ) const { TQValueList framesList; KoPoint pt; KWFrame * firstFrame = internalToDocument( TQPoint(0, y1), pt ); if ( !firstFrame ) return framesList; framesList.append( firstFrame ); uint frameIndex = const_cast(this)->m_frames.findRef( firstFrame ); while ( ++frameIndex < m_frames.count() ) { KWFrame* f = frame( frameIndex ); if ( f->internalY() > y2 ) // too far down, we're done break; framesList.append( f ); } return framesList; } // Helper for adjust*. There are 3 ways to use this method. // 1) marginLeft and marginRight set -> determination of left and right margins for adjustMargins // 2) marginRight set -> determination of right margin for adjustRMargin // 3) breakBegin, breakEnd set -> check whether we should jump over some frames // [when there is not enough space besides them] // reqMinWidth is the width that the formatter wants to use (current char/item) // validHeight is set to the height where marginLeft/marginRight applies (TODO) void KWTextFrameSet::getMargins( int yp, int h, int reqMinWidth, int* marginLeft, int* marginRight, int* pageWidth, int* validHeight, int* breakBegin, int* breakEnd, KoTextParag* parag ) { // paragLeftMargin will be used as the minimum width needed for the parag, // to "see" the parag. // So we only apply the first line margin if it increases that width, i.e. if > 0. // Otherwise only the first line might be visible, in a narrow passage. int paragLeftMargin = parag ? parag->leftMargin() : 0; if ( parag && !parag->string()->isRightToLeft() && parag->firstLineMargin() > 0 ) paragLeftMargin += parag->firstLineMargin(); #ifdef DEBUG_MARGINS kdDebugBody(32002) << " KWTextFrameSet " << this << "(" << name() << ") getMargins yp=" << yp << " h=" << h << " called by " << (marginLeft && marginRight ? "adjustMargins" : "formatVertically") << " paragLeftMargin=" << paragLeftMargin << endl; // Both or none... if (breakBegin) assert(breakEnd); if (breakEnd) assert(breakBegin); // Idem if ( marginLeft ) { assert( marginRight ); assert( pageWidth ); } #endif // List of text frames holding the paragraph (yp,yp+h) // Usually there is only one, but you can have a paragraph // starting in one frame/column and ending in another one. TQValueList textFrames = framesFromTo( yp, yp + h ); if (textFrames.isEmpty()) { #ifdef DEBUG_MARGINS kdDebug(32002) << " getMargins: internalToDocument returned no text frames for y1=" << yp << " y2=" << yp + h << " ->aborting with 0 margins" << endl; #endif // frame == 0 happens when the parag is under the last frame. // On an auto-resizable frame, we know the frame will grow so we can go ahead // and use its width. if ( !m_frames.isEmpty() && m_frames.last()->frameBehavior() == KWFrame::AutoExtendFrame ) { textFrames.append( m_frames.last() ); } else { // On auto-create-new-frame, this means the parag is on a not-yet-created page // (formatMore will notice afterwards) // Abort then, no need to return precise values // We also abort in the third case (Ignore) if ( validHeight ) *validHeight = 0; return; } } else { #ifdef DEBUG_MARGINS kdDebugBody(32002) << " getMargins: internalToDocument returned " << textFrames.count() << " frames holding this paragraph" << endl; #endif } if ( validHeight ) *validHeight = h; // TODO // Everything from there is in layout units // Note: it is very important that this method works in internal coordinates. // Otherwise, parags broken at the line-level (e.g. between two columns) are seen // as still in one piece, and we miss the frames in the 2nd column. int from = 0; // TODO support for variable width... maybe it's enough to take the max here int to = m_doc->ptToLayoutUnitPixX( textFrames.first()->innerWidth() ); if ( pageWidth ) *pageWidth = to; bool init = false; #ifdef DEBUG_MARGINS kdDebugBody(32002) << " getMargins: looking for frames between " << yp << " and " << yp+h << " (internal coords)" << endl; #endif if ( m_doc->layoutViewMode()->shouldAdjustMargins() ) { // Principle: for every frame on top at this height, we'll move from and to // towards each other. The text flows between 'from' and 'to' for ( TQValueList::const_iterator txtit = textFrames.begin(), txtend = textFrames.end() ; txtit != txtend ; ++txtit ) { KWFrame* theFrame = *txtit; Q_ASSERT( theFrame->frameStack() ); TQValueList onTop = theFrame->frameStack()->framesOnTop(); for (TQValueListIterator fIt = onTop.begin(); from < to && fIt != onTop.end(); ++fIt ) { if ( (*fIt)->runAround() == KWFrame::RA_BOUNDINGRECT ) { KoRect rectOnTop = theFrame->intersect( (*fIt)->runAroundRect() ); #ifdef DEBUG_MARGINS kdDebugBody(32002) << " getMargins found frame on top " << (*fIt)->frameSet()->name() << " with rect-on-top at (normal coords) " << rectOnTop << endl; #endif TQPoint iTop, iBottom; // top and bottom of intersection in internal coordinates if ( documentToInternal( rectOnTop.topLeft(), iTop ) && iTop.y() <= yp + h && // optimization documentToInternal( rectOnTop.bottomRight(), iBottom ) ) { #ifdef DEBUG_MARGINS kdDebugBody(32002) << " in internal coords: " << TQRect(iTop,iBottom) << endl; #endif // Look for intersection between yp -- yp+h and iTop -- iBottom if ( TQMAX( yp, iTop.y() ) <= TQMIN( yp+h, iBottom.y() ) ) { #ifdef DEBUG_MARGINS kdDebugBody(32002) << " getMargins iTop=" << iTop.x() << "," << iTop.y() << " iBottom=" << iBottom.x() << "," << iBottom.y() << endl; #endif int availLeft = TQMAX( 0, iTop.x() - from ); int availRight = TQMAX( 0, to - iBottom.x() ); #ifdef DEBUG_MARGINS kdDebugBody(32002) << " getMargins availLeft=" << availLeft << " availRight=" << availRight << endl; #endif bool chooseLeft = false; switch ( (*fIt)->runAroundSide() ) { case KWFrame::RA_LEFT: chooseLeft = true; break; case KWFrame::RA_RIGHT: break; // chooseLeft remains false case KWFrame::RA_BIGGEST: chooseLeft = ( availLeft > availRight ); // choose the max }; if ( chooseLeft ) // flow text at the left of the frame to = TQMIN( to, from + availLeft - 1 ); // can only go left -> TQMIN else // flow text at the right of the frame from = TQMAX( from, to - availRight + 1 ); // can only go right -> TQMAX #ifdef DEBUG_MARGINS kdDebugBody(32002) << " getMargins from=" << from << " to=" << to << endl; #endif // If the available space is too small, give up on it if ( to - from < reqMinWidth + paragLeftMargin ) { #ifdef DEBUG_MARGINS kdDebugBody(32002) << " smaller than minimum=" << m_doc->ptToLayoutUnitPixX( 15 ) + paragLeftMargin << endl; #endif from = to; } if ( breakEnd && from == to ) // no-space case { if ( !init ) // first time { init = true; *breakBegin = iTop.y(); *breakEnd = iBottom.y(); } else { *breakBegin = TQMIN( *breakBegin, iTop.y() ); *breakEnd = TQMAX( *breakEnd, iBottom.y() ); } #ifdef DEBUG_MARGINS kdDebugBody(32002) << " getMargins iBottom.y=" << iBottom.y() << " breakBegin=" << *breakBegin << " breakEnd=" << *breakEnd << endl; #endif } } // else no intersection }// else we got a 0L, or the iTop.y()<=yp+h test didn't work - wrong debug output // kdDebugBody(32002) << " gerMargins: normalToInternal returned 0L" << endl; } } } } if ( marginLeft /*&& marginRight && pageWidth -- implicit*/ ) { #ifdef DEBUG_MARGINS kdDebugBody(32002) << " getMargins done. from=" << from << " to=" << to << endl; #endif if ( from == to ) { from = 0; to = *pageWidth; } if ( marginLeft ) *marginLeft += from; if ( marginRight ) { #ifdef DEBUG_MARGINS kdDebug(32002) << " getMargins " << name() << " page width=" << *pageWidth << " to=" << to << endl; #endif *marginRight += *pageWidth - to; } } } void KWTextFrameSet::adjustMargins( int yp, int h, int reqMinWidth, int& leftMargin, int& rightMargin, int& pageWidth, KoTextParag* parag ) { #ifdef DEBUG_MARGINS kdDebugBody(32002) << "KWTextFrameSet::adjustMargins called for paragraph " << (parag?parag->paragId():-1) << endl; #endif int validHeight; // currently ignored (TODO) getMargins( yp, h, reqMinWidth, &leftMargin, &rightMargin, &pageWidth, &validHeight, 0L, 0L, parag ); #ifdef DEBUG_MARGINS kdDebugBody(32002) << "KWTextFrameSet::adjustMargins(yp=" << yp << " h=" << h << " reqMinWidth=" << reqMinWidth << " returning" << " leftMargin=" << leftMargin << " rightMargin=" << rightMargin << " valid from " << yp << " to " << yp+validHeight << endl; #endif } // helper for formatVertically bool KWTextFrameSet::checkVerticalBreak( int & yp, int & hp, KoTextParag * parag, bool linesTogether, int breakBegin, int breakEnd ) { // We need the "+1" here because when skipping a frame on top, we want to be _under_ // its bottom. Without the +1, we hit the frame again on the next adjustLMargin call. // Check for intersection between the parag (yp -- yp+hp) and the break area (breakBegin -- breakEnd) if ( TQMAX( yp, breakBegin ) <= TQMIN( yp+hp, breakEnd ) ) { if ( !parag || linesTogether ) // Paragraph-level breaking { #ifdef DEBUG_FORMATVERTICALLY kdDebug(32002) << "checkVerticalBreak ADJUSTING yp=" << yp << " hp=" << hp << " breakEnd+2 [new value for yp]=" << breakEnd+2 << endl; #endif yp = breakEnd + 1; return true; } else // Line-level breaking { TQMap& lineStarts = parag->lineStartList(); #ifdef DEBUG_FORMATVERTICALLY kdDebug(32002) << "checkVerticalBreak parag " << parag->paragId() << ". lineStarts has " << lineStarts.count() << " items" << endl; #endif int dy = 0; int line = 0; TQMap::Iterator it = lineStarts.begin(); for ( ; it != lineStarts.end() ; ++it, ++line ) { KoTextParagLineStart * ls = it.data(); Q_ASSERT( ls ); int y = parag->rect().y() + ls->y; #ifdef DEBUG_FORMATVERTICALLY kdDebug(32002) << "checkVerticalBreak parag " << parag->paragId() << " line " << line << " ls->y=" << ls->y << " ls->h=" << ls->h << " y=" << y << " breakBegin=" << breakBegin << " breakEnd=" << breakEnd << endl; #endif if ( !dy ) { if ( TQMAX( y, breakBegin ) <= TQMIN( y + ls->h, breakEnd ) ) { if ( line == 0 ) // First line ? It's like a paragraph breaking then { #ifdef DEBUG_FORMATVERTICALLY kdDebug(32002) << "checkVerticalBreak parag " << parag->paragId() << " BREAKING first line -> parag break" << endl; #endif yp = breakEnd + 1; return true; } dy = breakEnd + 1 - y; ls->y = breakEnd + 1 - parag->rect().y(); #ifdef DEBUG_FORMATVERTICALLY kdDebug(32002) << "checkVerticalBreak parag " << parag->paragId() << " BREAKING at line " << line << " dy=" << dy << " Setting ls->y to " << ls->y << ", y=" << breakEnd << endl; #endif } } else { ls->y += dy; #ifdef DEBUG_FORMATVERTICALLY if ( dy ) kdDebug(32002) << " moving down to position ls->y=" << ls->y << endl; #endif } } parag->setMovedDown( true ); parag->setHeight( hp + dy ); #ifdef DEBUG_FORMATVERTICALLY kdDebug(32002) << "Paragraph height set to " << hp+dy << endl; #endif hp += dy; return true; } // End of line-level breaking } return false; } int KWTextFrameSet::formatVertically( KoTextParag * _parag, const TQRect& paragRect ) { // WARNING: in this whole method parag can be 0. See adjustFlow() KWTextParag *parag = static_cast( _parag ); if ( !m_doc->layoutViewMode()->shouldFormatVertically() ) { return 0; } #ifdef DEBUG_FORMATVERTICALLY kdDebugBody(32002) << "KWTextFrameSet::formatVertically called for paragraph " << (parag?parag->paragId():-1) << endl; #endif int yp = paragRect.y(); int hp = paragRect.height(); int oldHeight = hp; int oldY = yp; // This is called by KoTextFormatter to apply "vertical breaks". // End of frames/pages lead to those "vertical breaks". // What we do, is adjust the Y accordingly, // to implement page-break at the paragraph level and at the line level. // It's cumulative (the space of one break will be included in the further // paragraph's y position), which makes it easy to implement. // But don't forget that formatVertically is called twice for every parag, since the formatting // is re-done after moving down. bool linesTogether = parag ? parag->linesTogether() : true; bool hardFrameBreak = parag ? parag->hardFrameBreakBefore() : false; if ( !hardFrameBreak && parag && parag->prev() ) hardFrameBreak = static_cast(parag->prev())->hardFrameBreakAfter(); #ifdef DEBUG_FORMATVERTICALLY kdDebugBody(32002) << "KWTextFrameSet::formatVertically parag=" << parag << " linesTogether=" << linesTogether << " hardFrameBreak=" << hardFrameBreak << " yp=" << yp << " hp=" << hp << endl; #endif int totalHeight = 0; TQPtrListIterator frameIt( frameIterator() ); for ( ; frameIt.current(); ++frameIt ) { int frameHeight = kWordDocument()->ptToLayoutUnitPixY( frameIt.current()->innerHeight() ); int bottom = totalHeight + frameHeight; // Only skip bottom of frame if there is a next one or if there'll be another one created. // ( Not for header/footer, for instance. ) bool check = frameIt.atLast() && frameIt.current()->frameBehavior() == KWFrame::AutoCreateNewFrame; if ( !check ) { // ## TODO optimize this [maybe we should simply start from the end in the main loop?] // Or cache the attribute ( e.g. "frame->hasCopy()" ). TQPtrListIterator nextFrame( frameIt ); while ( !check && !nextFrame.atLast() ) { ++nextFrame; if ( !nextFrame.current()->isCopy() ) check = true; // Found a frame after us that isn't a copy => we have somewhere for our overflow } } if ( check ) { if ( hardFrameBreak && yp > totalHeight && yp < bottom && !parag->wasMovedDown() ) { // The paragraph wants a frame break before it, and is in the current frame // The last check is for whether we did the frame break already // (formatVertically is called twice for each paragraph, if a break was done) yp = bottom /*+ 2*/; #ifdef DEBUG_FORMATVERTICALLY kdDebug(32002) << "KWTextFrameSet::formatVertically -> HARD FRAME BREAK" << endl; kdDebug(32002) << "KWTextFrameSet::formatVertically yp now " << yp << endl; #endif break; } #ifdef DEBUG_FORMATVERTICALLY kdDebug(32002) << " formatVertically: frameHeight=" << frameHeight << " bottom=" << bottom << endl; #endif // don't move down parags that have only one line and are bigger than the page (e.g. floating tables) if ( hp < frameHeight || ( parag && parag->lineStartList().count() > 1 ) ) { // breakBegin==breakEnd==bottom, since the next frame's top is the same as bottom, in TQRT coords. (void) checkVerticalBreak( yp, hp, parag, linesTogether, bottom, bottom ); // Some people write a single paragraph over 3 frames! So we have to keep looking, that's why we ignore the return value } } if ( yp+hp < bottom ) break; // we've been past the parag, so stop here totalHeight = bottom; } #ifdef DEBUG_FORMATVERTICALLY kdDebug(32002) << " formatVertically: now looking at RA_SKIP" << endl; #endif // Another case for a vertical break is frames with the RA_SKIP flag // Currently looking at all frames on top of all of our frames... maybe optimize better frameIt.toFirst(); for ( ; frameIt.current(); ++frameIt ) { Q_ASSERT( frameIt.current()->frameStack() ); TQValueList onTop = frameIt.current()->frameStack()->framesOnTop(); for (TQValueListIterator fIt = onTop.begin(); fIt != onTop.end(); ++fIt ) { if ( (*fIt)->runAround() == KWFrame::RA_SKIP ) { KoRect rectOnTop = frameIt.current()->intersect( (*fIt)->runAroundRect() ); TQPoint iTop, iBottom; // top and bottom in internal coordinates if ( documentToInternal( rectOnTop.topLeft(), iTop ) && iTop.y() <= yp + hp && documentToInternal( rectOnTop.bottomLeft(), iBottom ) && checkVerticalBreak( yp, hp, parag, linesTogether, iTop.y(), iBottom.y() ) ) { kdDebug(32002) << "KWTextFrameSet::formatVertically breaking around RA_SKIP frame yp="<string()->at( 0 ).width : 0; getMargins( yp, hp, reqMinWidth, 0L, 0L, 0L, 0L, &breakBegin, &breakEnd, parag ); if ( breakEnd ) { kdDebug(32002) << "KWTextFrameSet("<setHeight( hp ); if ( yp != oldY ) { TQRect r = parag->rect(); r.moveBy( 0, yp - oldY ); parag->setRect( r ); parag->setMovedDown( true ); } } #ifdef DEBUG_FORMATVERTICALLY kdDebug() << "KWTextFrameSet::formatVertically returning " << ( yp + hp ) - ( oldY + oldHeight ) << endl; #endif return ( yp + hp ) - ( oldY + oldHeight ); } // adjustFlow is called e.g. to break the "top margin" of a paragraph. // There is no parag pointer in that case. int KWTextFrameSet::adjustFlow( int y, int w, int h ) { TQRect r( 0, y, w, h ); return formatVertically( 0L, r ); } void KWTextFrameSet::fixParagWidth( KWTextParag* parag ) { // Fixing the parag rect for the formatting chars (CR and frame break). if ( parag && m_doc->viewFormattingChars() && parag->rect().width() < textDocument()->width() ) { if ( parag->hardFrameBreakAfter() ) { KoTextFormat * lastFormat = parag->at( parag->length() - 1 )->format(); const TQFontMetrics& refFontMetrics = lastFormat->refFontMetrics(); // keep in sync with KWTextFrameSet::formatVertically TQString str = i18n( "--- Frame Break ---" ); int width = refFontMetrics.width( str ); parag->setWidth( TQMIN( parag->rect().width() + width, textDocument()->width() ) ); } else // default KoTextFormatter implementation parag->fixParagWidth( true ); } } KWTextFrameSet::~KWTextFrameSet() { textDocument()->takeFlow(); //kdDebug(32001) << "KWTextFrameSet::~KWTextFrameSet" << endl; m_doc = 0L; delete m_textobj; } // This struct is used for sorting frames. // Since pages are one below the other, simply sorting on (y, x) does what we want. struct FrameStruct { KWFrame * frame; bool operator < ( const FrameStruct & t ) const { return compare(frame, t.frame) < 0; } bool operator <= ( const FrameStruct & t ) const { return compare(frame, t.frame) <= 0; } bool operator > ( const FrameStruct & t ) const { return compare(frame, t.frame) > 0; } /* the sorting of all frames in the same frameset is done as all sorting based on a simple frameOne > frameTwo question. Frame frameOne is greater then frameTwo if the center point lies more down then (the whole of) frame frameTwo. When they are equal, the X position is considered. */ int compare (const KWFrame *frameOne, const KWFrame *frameTwo) const { // The first criteria is the page number though! int pageOne = frameOne->pageNumber(); int pageTwo = frameTwo->pageNumber(); if( (pageOne == -1) ^ (pageTwo == -1)) { if( pageOne == -1 ) return 5; // undefined is higher than defined. return -5; } if ( pageOne > pageTwo ) return 4; // frameOne > frameTwo if ( pageOne < pageTwo ) return -4; // frameOne < frameTwo double centerX = frameOne->left() + (frameOne->width() /2); // reverse the return values of the next two for RTL if ( centerX > frameTwo->right()) return 3; // frameOne > frameTwo if ( centerX < frameTwo->left()) return -3; // frameOne < frameTwo // check the Y position. Y is greater only when it is below the other frame. double centerY = frameOne->top() + (frameOne->height() /2); if ( centerY > frameTwo->bottom() ) return 2; // frameOne > frameTwo if ( centerY < frameTwo->top() ) return -2; // frameOne < frameTwo // the center of frameOne lies inside frameTwo. Lets check the topleft pos. if (frameOne->top() > frameTwo->top()) return 1; return -1; } }; void KWTextFrameSet::updateFrames( int flags ) { // Not visible ? Don't bother then. if ( !isVisible() ) { //kdDebug(32002) << "KWTextFrameSet::updateFrames " << name() << " not visible" << endl; m_textobj->setVisible(false); return; } m_textobj->setVisible(true); //kdDebug(32002) << "KWTextFrameSet::updateFrames " << name() << " frame-count=" << m_frames.count() << endl; // Sort frames of this frameset on (y coord, x coord) // Adjustment on 20-Jun-2002 which does not change the itent of this but moves the // sorting from top-left of frame to the whole frame area. (TZ) TQValueList sortedFrames; int width = 0; TQPtrListIterator frameIter( frameIterator() ); for ( ; frameIter.current(); ++frameIter ) { // Calculate max width while we're at it //kdDebug(32002) << "KWTextFrameSet::updateFrames frame " << *frameIter.current() << " innerWidth=" << frameIter.current()->innerWidth() << "pt" << endl; width = TQMAX( width, m_doc->ptToLayoutUnitPixX( frameIter.current()->innerWidth())); if ( flags & SortFrames ) { FrameStruct str; str.frame = frameIter.current(); sortedFrames.append( str ); } } if ( width != textDocument()->width() ) { //kdDebug(32002) << "KWTextFrameSet::updateFrames setWidth " << width << " LU pixels." << endl; //textDocument()->setMinimumWidth( -1, 0 ); textDocument()->setWidth( width + 1 ); // TQRect semantics problem (#32866) } //else kdDebug(32002) << "KWTextFrameSet::updateFrames width already " << width << " LU pixels." << endl; if ( flags & SortFrames ) { qHeapSort( sortedFrames ); // Re-fill the frames list with the frames in the right order m_frames.setAutoDelete( false ); m_frames.clear(); TQValueList::Iterator it = sortedFrames.begin(); for ( ; it != sortedFrames.end() ; ++it ) m_frames.append( (*it).frame ); } double availHeight = 0; double internalYpt = 0; double lastRealFrameHeight = 0; bool firstFrame = true; TQPtrListIterator frameIt( m_frames ); for ( ; frameIt.current(); ++frameIt ) { KWFrame* theFrame = frameIt.current(); if ( !theFrame->isCopy() ) internalYpt += lastRealFrameHeight; theFrame->setInternalY( internalYpt ); // Update availHeight with the internal height of this frame - unless it's a copy if ( !theFrame->isCopy() || firstFrame ) { lastRealFrameHeight = theFrame->innerHeight(); availHeight += lastRealFrameHeight; } firstFrame = false; } m_textobj->setAvailableHeight( m_doc->ptToLayoutUnitPixY( availHeight ) ); //kdDebug(32002) << this << " (" << name() << ") KWTextFrameSet::updateFrames availHeight=" << availHeight // << " (LU: " << m_doc->ptToLayoutUnitPixY( availHeight ) << ")" << endl; m_frames.setAutoDelete( true ); KWFrameSet::updateFrames( flags ); } int KWTextFrameSet::availableHeight() const { return m_textobj->availableHeight(); } KWFrame * KWTextFrameSet::internalToDocument( const KoPoint &relPoint, KoPoint &dPoint ) const { #ifdef DEBUG_ITD kdDebug() << name() << " ITD called for relPoint=" << relPoint.x() << "," << relPoint.y() << endl; #endif if ( !m_doc->layoutViewMode()->hasFrames() ) { // text viewmode dPoint = relPoint; return m_frames.getFirst(); } // This does a binary search in the m_framesInPage array, with internalY as criteria // We only look at the first frame of each page. Refining is done later on. Q_ASSERT( !m_framesInPage.isEmpty() ); int len = m_framesInPage.count(); int n1 = 0; int n2 = len - 1; double internalY = 0.0; int mid = 0; bool found = FALSE; while ( n1 <= n2 ) { double res; mid = (n1 + n2)/2; #ifdef DEBUG_ITD kdDebug() << "ITD: begin. mid=" << mid << endl; #endif Q_ASSERT( m_framesInPage[mid] ); // We have no null items if ( m_framesInPage[mid]->isEmpty() ) res = -1; else { KWFrame * theFrame = m_framesInPage[mid]->first(); internalY = theFrame->internalY(); #ifdef DEBUG_ITD kdDebug() << "ITD: relPoint.y=" << relPoint.y() << " internalY=" << internalY << endl; #endif res = relPoint.y() - internalY; #ifdef DEBUG_ITD kdDebug() << "ITD: res=" << res << endl; #endif // Anything between this internalY (top) and internalY+height (bottom) is fine // (Using the next page's first frame's internalY only works if there is a frame on the next page) if ( res >= 0 ) { double height = theFrame->innerHeight(); #ifdef DEBUG_ITD kdDebug() << "ITD: height=" << height << " -> the bottom is at " << internalY+height << endl; #endif if ( relPoint.y() < internalY + height ) { #ifdef DEBUG_ITD kdDebug() << "ITD: found a match " << mid << endl; #endif found = true; break; } } } // res == 0 can't happen in theory, but in practice it happens when a frame has a height of 0 // (e.g. newly imported table without correct row heights) if ( res < 0 ) n2 = mid - 1; else // if ( res >= 0 ) n1 = mid + 1; #ifdef DEBUG_ITD kdDebug() << "ITD: End of loop. n1=" << n1 << " n2=" << n2 << endl; #endif } if ( !found ) { // Not found (n2 < n1) // We might have missed the frame because n2 has many frames // (and we only looked at the first one). mid = n2; #ifdef DEBUG_ITD kdDebug() << "ITD: Setting mid to n2=" << mid << endl; #endif if ( mid < 0 ) { #ifdef DEBUG_ITD kdDebug(32002) << "KWTextFrameSet::internalToDocument " << relPoint.x() << "," << relPoint.y() << " before any frame of " << (void*)this << endl; #endif dPoint = relPoint; // "bah", I said above :) return 0L; } } // search to first of equal items // This happens with copied frames, which have the same internalY int result = mid; while ( mid - 1 >= 0 ) { mid--; if ( !m_framesInPage[mid]->isEmpty() ) { KWFrame * theFrame = m_framesInPage[mid]->first(); #ifdef DEBUG_ITD kdDebug() << "KWTextFrameSet::internalToDocument going back to page " << mid << " - frame: " << theFrame->internalY() << endl; #endif if ( theFrame->internalY() == internalY ) // same internalY as the frame we found before result = mid; else break; } } // Now iterate over the frames in page 'result' and find the right one TQPtrListIterator frameIt( *m_framesInPage[result] ); for ( ; frameIt.current(); ++frameIt ) { KWFrame *theFrame = frameIt.current(); KoRect relRect( 0, theFrame->internalY(), theFrame->innerWidth(), theFrame->innerHeight() ); #ifdef DEBUG_ITD kdDebug() << "KWTextFrameSet::internalToDocument frame's relative rect:" << relRect << endl; #endif if ( relRect.contains( relPoint ) ) // both relRect and relPoint are in "relative coordinates" { dPoint = internalToDocumentKnowingFrame( relPoint, theFrame ); return theFrame; } } #ifdef DEBUG_ITD kdDebug(32002) << "KWTextFrameSet::internalToDocument " << relPoint.x() << "," << relPoint.y() << " not in any frame of " << (void*)this << " (looked on page " << result << ")" << endl; #endif dPoint = relPoint; // bah again return 0L; } // same but with iPoint in LU KWFrame * KWTextFrameSet::internalToDocument( const TQPoint &iPoint, KoPoint &dPoint ) const { KoPoint relPoint = m_doc->layoutUnitPtToPt( m_doc->pixelToPt( iPoint ) ); return internalToDocument( relPoint, dPoint ); } #ifndef NDEBUG void KWTextFrameSet::printDebug() { KWFrameSet::printDebug(); if ( !isDeleted() ) { kdDebug() << "KoTextDocument width = " << textDocument()->width() << " height = " << textDocument()->height() << endl; } TQPtrListIterator cit( textDocument()->allCustomItems() ); for ( ; cit.current() ; ++cit ) { KWAnchor *anc = dynamic_cast( cit.current() ); if (anc) kdDebug() << "Inline framesets: " << anc->frameSet()->name() << endl; } } #endif TQDomElement KWTextFrameSet::saveInternal( TQDomElement &parentElem, bool saveFrames, bool saveAnchorsFramesets ) { if ( m_frames.isEmpty() ) // Deleted frameset -> don't save return TQDomElement(); TQDomElement framesetElem = parentElem.ownerDocument().createElement( "FRAMESET" ); parentElem.appendChild( framesetElem ); if ( m_groupmanager ) { framesetElem.setAttribute( "grpMgr", m_groupmanager->name() ); KWTableFrameSet::Cell *cell = (KWTableFrameSet::Cell *)this; framesetElem.setAttribute( "row", cell->firstRow() ); framesetElem.setAttribute( "col", cell->firstColumn() ); framesetElem.setAttribute( "rows", cell->rowSpan() ); framesetElem.setAttribute( "cols", cell->columnSpan() ); } if ( protectContent() ) framesetElem.setAttribute( "protectContent", static_cast(protectContent())); KWFrameSet::saveCommon( framesetElem, saveFrames ); // Save paragraphs KWTextParag *start = static_cast( textDocument()->firstParag() ); while ( start ) { start->save( framesetElem, saveAnchorsFramesets ); start = static_cast( start->next() ); } return framesetElem; } KWFrame* KWTextFrameSet::loadOasisTextFrame( const TQDomElement& frameTag, const TQDomElement &tag, KoOasisContext& context ) { context.styleStack().save(); context.fillStyleStack( frameTag, KoXmlNS::draw, "style-name", "graphic" ); // get the style for the graphics element KWFrame* frame = loadOasisFrame( frameTag, context ); // Load minimum height - only available for text-box bool hasMinHeight = tag.hasAttributeNS( KoXmlNS::fo, "min-height" ); if ( hasMinHeight ) { double height = KoUnit::parseValue( tag.attributeNS( KoXmlNS::fo, "min-height", TQString() ) ); frame->setMinimumFrameHeight( height ); if ( height > frame->height() || !tag.hasAttributeNS( KoXmlNS::fo, "height" ) ) frame->setHeight( height ); } // Load overflow behavior (OASIS 14.27.27, not in OO-1.1 DTD). This is here since it's only for text framesets. const TQString overflowBehavior = context.styleStack().attributeNS( KoXmlNS::style, "overflow-behavior" ); if ( frame->minimumFrameHeight() > 0 ) frame->setFrameBehavior( KWFrame::AutoExtendFrame ); else if ( overflowBehavior == "auto-create-new-frame" ) { frame->setFrameBehavior( KWFrame::AutoCreateNewFrame ); frame->setNewFrameBehavior( KWFrame::Reconnect ); // anything else doesn't make sense } else if ( overflowBehavior.isEmpty() || overflowBehavior == "clip" ) frame->setFrameBehavior( KWFrame::Ignore ); else kdWarning(32001) << "Unknown value for style:overflow-behavior: " << overflowBehavior << endl; context.styleStack().restore(); return frame; } void KWTextFrameSet::loadOasisContent( const TQDomElement &bodyElem, KoOasisContext& context ) { return m_textobj->loadOasisContent( bodyElem, context, m_doc->styleCollection() ); } KWFrame* KWTextFrameSet::loadOasis( const TQDomElement& frameTag, const TQDomElement &tag, KoOasisContext& context ) { KWFrame* frame = loadOasisTextFrame( frameTag, tag, context ); loadOasisContent( tag, context ); return frame; } static void finishTOC( KoXmlWriter& writer ) { writer.endElement(); // text:table-of-content writer.endElement(); // text:index-body } void KWTextFrameSet::saveOasisContent( KoXmlWriter& writer, KoSavingContext& context ) const { // TODO save protectContent TQMap bookmarksPerParagraph; if ( m_doc->bookmarkList() ) bookmarksPerParagraph = m_doc->bookmarkList()->bookmarksPerParagraph(); // Basically just call saveOasis on every paragraph. // But we do table-of-contents-handling (for kword) in addition, // as well as bookmarks. KoTextParag* parag = textDocument()->firstParag(); bool inTOC = false; while ( parag ) { bool tocParag = parag->partOfTableOfContents(); if ( tocParag != inTOC ) { if ( tocParag ) { // first TOC paragraph writer.startElement( "text:table-of-content" ); writer.addAttribute( "text:name", "Table Of Contents" ); writer.addAttribute( "text:protected", "false" ); // true by default in OO, but we don't support that yet anyway writer.startElement( "text:table-of-content-source" ); // TODO writer.addAttribute( "text:outline-level", ... ); // TODO for each level writer.startElement( "text:table-of-content-entry-template" ); // TODO writer.endElement(); // text:table-of-content-entry-template writer.endElement(); // text:table-of-content-source writer.startElement( "text:index-body" ); writer.startElement( "text:index-title" ); writer.addAttribute( "text:name", "Table Of Contents Heading" ); } else { finishTOC( writer ); } } KoSavingContext::BookmarkPositions bookmarkStarts, bookmarkEnds; TQMap::const_iterator bkit = bookmarksPerParagraph.find( parag ); if ( bkit != bookmarksPerParagraph.end() ) { // Massage a bit the bookmarks data; KoTextParag wants it ordered by position, for speed. const KoTextBookmarkList& bookmarks = *bkit; for ( KoTextBookmarkList::const_iterator it = bookmarks.begin(); it != bookmarks.end(); ++it ) { const KoTextBookmark& bk = *it; if ( bk.startParag() == parag ) bookmarkStarts.append( KoSavingContext::BookmarkPosition( bk.bookmarkName(), bk.bookmarkStartIndex(), bk.isSimple() ) ); if ( bk.endParag() == parag && !bk.isSimple() ) bookmarkEnds.append( KoSavingContext::BookmarkPosition( bk.bookmarkName(), bk.bookmarkEndIndex(), false ) ); } qHeapSort( bookmarkStarts ); qHeapSort( bookmarkEnds ); } // should be done in all cases, even if both lists are empty context.setBookmarkPositions( bookmarkStarts, bookmarkEnds ); // Save the whole parag, without the trailing space. parag->saveOasis( writer, context, 0, parag->lastCharPos() ); if ( tocParag && !inTOC ) writer.endElement(); // text:index-title inTOC = tocParag; parag = parag->next(); } if ( inTOC ) finishTOC( writer ); } void KWTextFrameSet::saveOasis( KoXmlWriter& writer, KoSavingContext& context, bool saveFrames ) const { // Save first frame with the whole contents KWFrame* frame = m_frames.getFirst(); TQString lastFrameName = name(); frame->startOasisFrame( writer, context.mainStyles(), lastFrameName ); TQString nextFrameName = name() + "-"; writer.startElement( "draw:text-box" ); if ( frame->frameBehavior() == KWFrame::AutoExtendFrame ) writer.addAttributePt( "fo:min-height", frame->minimumFrameHeight() ); if ( m_frames.count() > 1 && saveFrames ) writer.addAttribute( "draw:chain-next-name", nextFrameName + "2" ); saveOasisContent( writer, context ); writer.endElement(); // draw:text-box writer.endElement(); // draw:frame // Save other frames using chaining if ( saveFrames ) // false when called from KWDocument::saveSelectedFrames { int frameNumber = 2; TQPtrListIterator frameIter( frameIterator() ); ++frameIter; // skip first frame, already saved for ( ; frameIter.current(); ++frameIter, ++frameNumber ) { const TQString frameName = nextFrameName + TQString::number( frameNumber ); frameIter.current()->startOasisFrame( writer, context.mainStyles(), frameName, lastFrameName ); lastFrameName = frameName; // this is used for copy-frames writer.startElement( "draw:text-box" ); if ( frame->frameBehavior() == KWFrame::AutoExtendFrame ) writer.addAttributePt( "fo:min-height", frame->minimumFrameHeight() ); if ( frameNumber < (int)m_frames.count() ) writer.addAttribute( "draw:chain-next-name", nextFrameName + TQString::number( frameNumber+1 ) ); // No contents. Well, OOo saves an empty paragraph, but I'd say that's wrong. writer.endElement(); writer.endElement(); // draw:frame } } } void KWTextFrameSet::load( TQDomElement &attributes, bool loadFrames ) { KWFrameSet::load( attributes, loadFrames ); if ( attributes.hasAttribute( "protectContent")) setProtectContent((bool)attributes.attribute( "protectContent" ).toInt()); textDocument()->clear(false); // Get rid of dummy paragraph (and more if any) m_textobj->setLastFormattedParag( 0L ); // no more parags, avoid UMR in next setLastFormattedParag call KWTextParag *lastParagraph = 0L; // TQDomElement paragraph = attributes.firstChild().toElement(); for ( ; !paragraph.isNull() ; paragraph = paragraph.nextSibling().toElement() ) { if ( paragraph.tagName() == "PARAGRAPH" ) { KWTextParag *parag = new KWTextParag( textDocument(), lastParagraph ); parag->load( paragraph ); if ( !lastParagraph ) // First parag textDocument()->setFirstParag( parag ); lastParagraph = parag; m_doc->progressItemLoaded(); } } if ( !lastParagraph ) // We created no paragraph { // Create an empty one, then. See KWTextDocument ctor. textDocument()->clear( true ); static_cast( textDocument()->firstParag() )->setStyle( m_doc->styleCollection()->findStyle( "Standard" ) ); } else textDocument()->setLastParag( lastParagraph ); m_textobj->setLastFormattedParag( textDocument()->firstParag() ); //kdDebug(32001) << "KWTextFrameSet::load done" << endl; } void KWTextFrameSet::finalize() { KWFrameSet::finalize(); m_textobj->formatMore( 0 ); // just to get the timer going // This is important in case of auto-resized frames or table cells, // which come from an import filter, which didn't give them the right size. // However it shouldn't start _now_ (so we use 0), because e.g. main frames // don't have the right size yet (KWFrameLayout not done yet). } void KWTextFrameSet::setVisible(bool visible) { setInlineFramesVisible( visible ); KWFrameSet::setVisible( visible ); } void KWTextFrameSet::setInlineFramesVisible(bool visible) { TQPtrListIterator cit( textDocument()->allCustomItems() ); for ( ; cit.current() ; ++cit ) { KWAnchor *anc = dynamic_cast( cit.current() ); if (anc) anc->frameSet()->setVisible( visible ); } } void KWTextFrameSet::addTextFrameSets( TQPtrList & lst, bool onlyReadWrite ) { if (!textObject()->protectContent() || !onlyReadWrite) lst.append(this); } void KWTextFrameSet::slotNewCommand( KCommand *cmd ) { m_doc->addCommand( cmd ); } void KWTextFrameSet::ensureFormatted( KoTextParag * parag, bool emitAfterFormatting ) { if (!isVisible()) return; m_textobj->ensureFormatted( parag, emitAfterFormatting ); } bool KWTextFrameSet::slotAfterFormattingNeedMoreSpace( int bottom, KoTextParag *lastFormatted ) { int availHeight = availableHeight(); #ifdef DEBUG_FORMAT_MORE if(lastFormatted) kdDebug(32002) << "slotAfterFormatting We need more space in " << name() << " bottom=" << bottom + lastFormatted->rect().height() << " availHeight=" << availHeight << endl; else kdDebug(32002) << "slotAfterFormatting We need more space in " << name() << " bottom2=" << bottom << " availHeight=" << availHeight << endl; #endif if ( m_frames.isEmpty() ) { kdWarning(32002) << "slotAfterFormatting no more space, but no frame !" << endl; return true; // abort } KWFrame::FrameBehavior frmBehavior = m_frames.last()->frameBehavior(); if ( frmBehavior == KWFrame::AutoExtendFrame && isProtectSize()) frmBehavior = KWFrame::Ignore; if ( frmBehavior == KWFrame::AutoCreateNewFrame ) { KWFrame *theFrame = settingsFrame( m_frames.last() ); double minHeight = s_minFrameHeight + theFrame->paddingTop() + theFrame->paddingBottom() + 5; if ( availHeight < minHeight ) frmBehavior = KWFrame::Ignore; } int difference = ( bottom + 2 ) - availHeight; // in layout unit pixels #ifdef DEBUG_FORMAT_MORE kdDebug(32002) << "AutoExtendFrame bottom=" << bottom << " availHeight=" << availHeight << " => difference = " << difference << endl; #endif if( lastFormatted && bottom + lastFormatted->rect().height() > availHeight ) { #ifdef DEBUG_FORMAT_MORE kdDebug(32002) << " next will be off -> adding " << lastFormatted->rect().height() << endl; #endif difference += lastFormatted->rect().height(); } switch ( frmBehavior ) { case KWFrame::AutoExtendFrame: { if(difference > 0) { // There's no point in resizing a copy, so go back to the last non-copy frame KWFrame *theFrame = settingsFrame( m_frames.last() ); double wantedPosition = 0; // Footers and footnotes go up if ( theFrame->frameSet()->isAFooter() || theFrame->frameSet()->isFootNote() ) { // The Y position doesn't matter much, recalcFrames will reposition the frame // But the point of this code is set the correct height for the frame. double maxFooterSize = footerHeaderSizeMax( theFrame ); double diffPt = m_doc->layoutUnitPtToPt( m_doc->pixelYToPt( difference ) ); wantedPosition = theFrame->top() - diffPt; #ifdef DEBUG_FORMAT_MORE kdDebug() << " diffPt=" << diffPt << " -> wantedPosition=" << wantedPosition << endl; #endif if ( wantedPosition < 0 ) { m_textobj->setLastFormattedParag( 0 ); return true; // abort } if ( wantedPosition != theFrame->top() && ( theFrame->frameSet()->isFootEndNote() || theFrame->bottom() - maxFooterSize <= wantedPosition ) ) // Apply maxFooterSize for footers only { theFrame->setTop( wantedPosition ); #ifdef DEBUG_FORMAT_MORE kdDebug() << " ok: frame=" << *theFrame << " bottom=" << theFrame->bottom() << " height=" << theFrame->height() << endl; #endif frameResized( theFrame, true ); // We only got room for the next paragraph, we still have to keep the formatting going... return false; // keep going } kdDebug() << "slotAfterFormatting didn't manage to get more space for footer/footnote, aborting" << endl; return true; // abort } // Other frames are resized by the bottom wantedPosition = m_doc->layoutUnitPtToPt( m_doc->pixelYToPt( difference ) ) + theFrame->bottom(); KWPage *page = m_doc->pageManager()->page( theFrame ); double pageBottom; if(page) pageBottom = page->offsetInDocument() + page->height() - page->bottomMargin(); else pageBottom = theFrame->bottom(); double newPosition = TQMIN( wantedPosition, pageBottom ); kdDebug(32002) << "wantedPosition=" << wantedPosition << " pageBottom=" << pageBottom << " -> newPosition=" << newPosition << endl; if ( theFrame->frameSet()->isAHeader() ) { double maxHeaderSize=footerHeaderSizeMax( theFrame ); newPosition = TQMIN( newPosition, maxHeaderSize + theFrame->top() ); } newPosition = TQMAX( newPosition, theFrame->top() ); // avoid negative heights kdDebug(32002) << "newPosition=" << newPosition << endl; bool resized = false; if(theFrame->frameSet()->groupmanager()) { KWTableFrameSet *table = theFrame->frameSet()->groupmanager(); #ifdef DEBUG_FORMAT_MORE kdDebug(32002) << "is table cell; just setting new minFrameHeight, to " << newPosition - theFrame->top() << endl; #endif double newMinFrameHeight = newPosition - theFrame->top(); resized = TQABS( newMinFrameHeight - theFrame->minimumFrameHeight() ) > 1E-10; if ( resized ) { theFrame->setMinimumFrameHeight( newMinFrameHeight ); KWTableFrameSet::Cell *cell = (KWTableFrameSet::Cell *)theFrame->frameSet(); table->recalcCols(cell->firstColumn(), cell->firstRow()); table->recalcRows(cell->firstColumn(), cell->firstRow()); if (! table->anchorFrameset() ) ;// do nothing else if ( table->anchorFrameset() && table->anchorFrameset()->isAHeader() ) //we must recalculate the header frame size { theFrame = table->anchorFrameset()->frameIterator().getLast(); theFrame->setBottom(newPosition); frameResized( theFrame, false ); } else if ( table->anchorFrameset()->isAFooter() || table->anchorFrameset()->isFootNote() ) //we must recalculate the footer frame size { theFrame = table->anchorFrameset()->frameIterator().getLast(); // The Y position doesn't matter much, recalcFrames will reposition the frame // But the point of this code is set the correct height for the frame. double maxFooterSize = footerHeaderSizeMax( theFrame ); double diffPt = m_doc->layoutUnitPtToPt( m_doc->pixelYToPt( difference ) ); wantedPosition = theFrame->top() - diffPt; if ( wantedPosition < 0 ) { m_textobj->setLastFormattedParag( 0 ); return true; // abort } if ( wantedPosition != theFrame->top() && ( theFrame->frameSet()->isFootEndNote() || theFrame->bottom() - maxFooterSize <= wantedPosition ) ) // Apply maxFooterSize for footers only { theFrame->setTop( wantedPosition ); frameResized( theFrame, true ); // We only got room for the next paragraph, we still have to keep the formatting going... } } m_doc->delayedRepaintAllViews(); } return true; // abort formatting for now (not sure this is correct) } else { resized = TQABS( theFrame->bottom() - newPosition ) > 1E-10; #ifdef DEBUG_FORMAT_MORE kdDebug() << " bottom=" << theFrame->bottom() << " new position:" << newPosition << " wantedPosition=" << wantedPosition << " resized=" << resized << endl; #endif if ( resized ) { #ifdef DEBUG_FORMAT_MORE kdDebug(32002) << "slotAfterFormatting changing bottom from " << theFrame->bottom() << " to " << newPosition << endl; #endif theFrame->setBottom(newPosition); frameResized( theFrame, false ); } } if(newPosition < wantedPosition && (theFrame->newFrameBehavior() == KWFrame::Reconnect && !theFrame->frameSet()->isEndNote())) // end notes are handled by KWFrameLayout { wantedPosition = wantedPosition - newPosition + theFrame->top() + page->height(); #ifdef DEBUG_FORMAT_MORE kdDebug(32002) << "Not enough room in this page -> creating new one, with a reconnect frame" << endl; kdDebug(32002) << "new wantedPosition=" << wantedPosition << endl; #endif // fall through to AutoCreateNewFrame } else if(newPosition < wantedPosition && (theFrame->newFrameBehavior() == KWFrame::NoFollowup)) { if ( theFrame->frameSet()->isEndNote() ) // we'll need a new page m_doc->delayedRecalcFrames( theFrame->pageNumber() ); m_textobj->setLastFormattedParag( 0 ); return true; // abort } else { if ( resized ) // we managed to resize a frame return false; // keep going return true; // abort } } } case KWFrame::AutoCreateNewFrame: { // We need a new frame in this frameset. return createNewPageAndNewFrame( lastFormatted, difference ); } case KWFrame::Ignore: #ifdef DEBUG_FORMAT_MORE kdDebug(32002) << "slotAfterFormatting frame behaviour is Ignore" << endl; #endif m_textobj->setLastFormattedParag( 0 ); return true; // abort } kdWarning() << "NEVERREACHED" << endl; // NEVERREACHED return true; } void KWTextFrameSet::slotAfterFormattingTooMuchSpace( int bottom ) { int availHeight = availableHeight(); // The + 2 here leaves 2 pixels below the last line. Without it we hit // the "break at end of frame" case in formatVertically (!!). int difference = availHeight - ( bottom + 2 ); #ifdef DEBUG_FORMAT_MORE kdDebug(32002) << "slotAfterFormatting less text than space (AutoExtendFrame). Frameset " << name() << " availHeight=" << availHeight << " bottom=" << bottom << " ->difference=" << difference << endl; #endif // There's no point in resizing a copy, so go back to the last non-copy frame KWFrame *theFrame = settingsFrame( m_frames.last() ); #ifdef DEBUG_FORMAT_MORE kdDebug(32002) << " frame is " << *theFrame << " footer:" << ( theFrame->frameSet()->isAFooter() || theFrame->frameSet()->isFootEndNote() ) << endl; #endif if ( theFrame->frameSet()->isAFooter() || theFrame->frameSet()->isFootEndNote() ) { double wantedPosition = theFrame->top() + m_doc->layoutUnitPtToPt( m_doc->pixelYToPt( difference ) ); Q_ASSERT( wantedPosition < theFrame->bottom() ); if ( wantedPosition != theFrame->top() ) { #ifdef DEBUG_FORMAT_MORE kdDebug() << " top= " << theFrame->top() << " setTop " << wantedPosition << endl; #endif theFrame->setTop( wantedPosition ); #ifdef DEBUG_FORMAT_MORE kdDebug() << " -> the frame is now " << *theFrame << endl; #endif frameResized( theFrame, true ); } } else // header or other frame: resize bottom { double wantedPosition = theFrame->bottom() - m_doc->layoutUnitPtToPt( m_doc->pixelYToPt( difference ) ); #ifdef DEBUG_FORMAT_MORE kdDebug() << "slotAfterFormatting wantedPosition=" << wantedPosition << " top+minheight=" << theFrame->top() + s_minFrameHeight << endl; #endif wantedPosition = TQMAX( wantedPosition, theFrame->top() + s_minFrameHeight ); if( theFrame->frameSet()->groupmanager() ) { if ( wantedPosition != theFrame->bottom()) { KWTableFrameSet *table = theFrame->frameSet()->groupmanager(); // When a frame can be smaller we don't rescale it if it is a table, since // we don't have the full picture of the change. // We will set the minFrameHeight to the correct value and let the tables code // do the rescaling based on all the frames in the row. (see KWTableFrameSet::recalcRows()) if(wantedPosition != theFrame->top() + theFrame->minimumFrameHeight()) { theFrame->setMinimumFrameHeight(wantedPosition - theFrame->top()); #ifdef DEBUG_FORMAT_MORE kdDebug(32002) << "is table cell; only setting new minFrameHeight to " << theFrame->minimumFrameHeight() << ", recalcrows will do the rest" << endl; #endif KWTableFrameSet::Cell *cell = (KWTableFrameSet::Cell *)theFrame->frameSet(); table->recalcCols(cell->firstColumn(), cell->firstRow()); table->recalcRows(cell->firstColumn(), cell->firstRow()); if (! table->anchorFrameset() ) ;// do nothing else if ( table->anchorFrameset() && table->anchorFrameset()->isAHeader() ) { theFrame = table->anchorFrameset()->frameIterator().getLast(); theFrame->setBottom(wantedPosition); frameResized( theFrame, false ); } else if ( table->anchorFrameset()->isAFooter() || table->anchorFrameset()->isFootEndNote() ) { theFrame = table->anchorFrameset()->frameIterator().getLast(); double wantedPosition = theFrame->top() + m_doc->layoutUnitPtToPt( m_doc->pixelYToPt( difference ) ); Q_ASSERT( wantedPosition < theFrame->bottom() ); if ( wantedPosition != theFrame->top() ) { theFrame->setTop( wantedPosition ); frameResized( theFrame, true ); } } m_doc->delayedRepaintAllViews(); } } } else { // Also apply the frame's minimum height wantedPosition = TQMAX( wantedPosition, theFrame->top() + theFrame->minimumFrameHeight() ); if ( wantedPosition != theFrame->bottom()) { #ifdef DEBUG_FORMAT_MORE kdDebug() << " the frame was " << *theFrame << endl; kdDebug() << "setBottom " << wantedPosition << endl; #endif theFrame->setBottom( wantedPosition ); #ifdef DEBUG_FORMAT_MORE kdDebug() << " -> the frame is now " << *theFrame << endl; #endif frameResized( theFrame, true ); } } } } void KWTextFrameSet::slotAfterFormatting( int bottom, KoTextParag *lastFormatted, bool* abort ) { int availHeight = availableHeight(); if ( ( bottom > availHeight ) || // this parag is already off page ( lastFormatted && bottom + lastFormatted->rect().height() > availHeight ) ) // or next parag will be off page { *abort = slotAfterFormattingNeedMoreSpace( bottom, lastFormatted ); } // Handle the case where the last frame is empty, so we may want to // remove the last page. else if ( m_frames.count() > 1 && !lastFormatted && frameSetInfo() == KWFrameSet::FI_BODY && bottom < availHeight - m_doc->ptToLayoutUnitPixY( m_frames.last()->innerHeight() ) ) { #ifdef DEBUG_FORMAT_MORE kdDebug(32002) << "slotAfterFormatting too much space (bottom=" << bottom << ", availHeight=" << availHeight << ") , trying to remove last frame" << endl; #endif // Remove the empty last frame, if it's an auto-created one (e.g. a // continuation on the next page). Not when the user just created it! if(m_frames.last()->frameBehavior() == KWFrame::AutoExtendFrame && m_frames.last()->minimumFrameHeight() < 1E-10 ) { // i.e. equal to 0 deleteFrame(m_frames.last(), true); m_doc->frameChanged( 0L ); } if ( m_doc->processingType() == KWDocument::WP ) { bool removed = m_doc->tryRemovingPages(); // Do all the recalc in one go. Speeds up deleting many pages. if ( removed ) m_doc->afterRemovePages(); } } // Handle the case where the last frame is in AutoExtendFrame mode // and there is less text than space else if ( !lastFormatted && bottom + 2 < availHeight && (m_frames.last()->frameBehavior() == KWFrame::AutoExtendFrame&& !isProtectSize()) ) { slotAfterFormattingTooMuchSpace( bottom ); *abort = false; } if ( m_doc->processingType() == KWDocument::WP && this == m_doc->frameSet( 0 ) ) { if ( m_lastTextDocHeight != textDocument()->height() ) { m_lastTextDocHeight = textDocument()->height(); emit mainTextHeightChanged(); } } } // This is called when a text frame with behaviour AutoCreateNewFrame // has more text than available frame height, so we need to create a new page // so that a followup frame is created for this one bool KWTextFrameSet::createNewPageAndNewFrame( KoTextParag* lastFormatted, int /*difference*/ ) { KWFrame* lastFrame = m_frames.last(); // This is only going to help us if the new frame is reconnected. Otherwise bail out. if ( !lastFrame || lastFrame->newFrameBehavior() != KWFrame::Reconnect ) { kdDebug(32002) << name() << " : frame is AutoCreateNewFrame but not Reconnect !?!? Aborting." << endl; m_textobj->setLastFormattedParag( 0 ); return true; // abort } //#ifdef DEBUG_FORMAT_MORE kdDebug(32002) << "createNewPageAndNewFrame creating new frame in frameset " << name() << endl; //#endif uint oldCount = m_frames.count(); int lastPageNumber = m_doc->pageManager()->lastPageNumber(); kdDebug(32002) << " last frame=" << lastFrame << " pagenum=" << lastFrame->pageNumber() << " lastPageNumber=" << lastPageNumber << " m_frames count=" << oldCount << endl; // First create a new page for it if necessary if ( lastFrame->pageNumber() == lastPageNumber ) { // Let's first check if it will give us more space than we // already have left in this page. Otherwise we'll loop infinitely. int heightWeWillGet = 0; // in LU if(isMainFrameset()) // is never added in the framesToCopyOnNewPage heightWeWillGet += m_doc->ptToLayoutUnitPixY( m_frames.last()->height() ); else { TQPtrList framesToCopy = m_doc->framesToCopyOnNewPage( lastPageNumber ); TQPtrListIterator frameIt( framesToCopy ); for ( ; frameIt.current(); ++frameIt ) if (frameIt.current()->frameSet() == this && frameIt.current()->newFrameBehavior()==KWFrame::Reconnect) heightWeWillGet += m_doc->ptToLayoutUnitPixY( frameIt.current()->height() ); } // This logic doesn't applies to tables though, since they can be broken over multiple pages // TODO: lastFormatted->containsTable() or so (containsPageBreakableItem rather). // "difference" doesn't apply if we're pasting multiple paragraphs. // We want to compare the height of one paragraph, not all the missing height. KoTextParag* parag = lastFormatted ? lastFormatted : textDocument()->lastParag(); // In fact the parag height isn't the right thing to test for - we should check // for the highest character that remains to be positioned. // Testcase: many big inline pictures in one paragraph. int paragHeight = parag->rect().height(); kdDebug(32002) << "height we will get in the new page:" << heightWeWillGet << " parag " << parag << " height:" << paragHeight << endl; if ( heightWeWillGet < paragHeight && !m_groupmanager ) { kdDebug(32002) << "not enough height on the new page, not worth it" << endl; m_textobj->setLastFormattedParag( 0 ); return true; // abort } KWPage *page = m_doc->appendPage(); if ( !m_doc->isLoading() ) m_doc->afterInsertPage( page->pageNumber() ); kdDebug(32002) << "now frames count=" << m_frames.count() << endl; } // Maybe creating the new page created the frame in this frameset, then we're done // Otherwise let's create it ourselves: if ( m_frames.count() == oldCount ) { Q_ASSERT( !isMainFrameset() ); // ouch, should have gone to the appendPage case above... // Otherwise, create a new frame on next page kdDebug(32002) << "createNewPageAndNewFrame creating frame on page " << lastFrame->pageNumber()+1 << endl; KWFrame *frm = lastFrame->getCopy(); frm->moveBy( 0, m_doc->pageManager()->page(frm)->height() ); addFrame( frm ); } updateFrames(); Q_ASSERT(frame(0) && frame(0)->frameStack()); frame(0)->frameStack()->update(); /// We don't want to start from the beginning every time ! ////m_doc->invalidate(); // Reformat the last paragraph. If it's over the two pages, it will need // the new page (e.g. for inline frames that need internalToDocument to work) if ( lastFormatted ) lastFormatted = lastFormatted->prev(); else lastFormatted = textDocument()->lastParag(); if ( lastFormatted ) { m_textobj->setLastFormattedParag( lastFormatted ); lastFormatted->invalidate( 0 ); //This was a way to format the rest from here (recursively), but it didn't help much ensureCursorVisible() //So instead I fixed formatMore to return formatMore(2) itself. //m_textobj->formatMore( 2 ); return false; // keep going } m_doc->delayedRepaintAllViews(); return false; // all done } double KWTextFrameSet::footNoteSize( KWFrame *theFrame ) { double tmp =0.0; int page = theFrame->pageNumber(); TQPtrListIterator fit = m_doc->framesetsIterator(); for ( ; fit.current() ; ++fit ) { if((fit.current()->isFootNote() || fit.current()->isEndNote()) && fit.current()->isVisible()) { KWFrame * frm=fit.current()->frame( 0 ); if(frm->pageNumber()==page ) tmp += frm->innerHeight()+m_doc->ptFootnoteBodySpacing(); } } return tmp; } double KWTextFrameSet::footerHeaderSizeMax( KWFrame *theFrame ) { KWPage *page = m_doc->pageManager()->page(theFrame); Q_ASSERT( page ); if ( !page ) return 0; double tmp = page->height() - page->bottomMargin() - page->topMargin() - 40;//default min 40 for page size bool header=theFrame->frameSet()->isAHeader(); if( header ? m_doc->isHeaderVisible():m_doc->isFooterVisible() ) { TQPtrListIterator fit = m_doc->framesetsIterator(); for ( ; fit.current() ; ++fit ) { bool state = header ? fit.current()->isAFooter():fit.current()->isAHeader(); if(fit.current()->isVisible() && state) { KWFrame * frm=fit.current()->frame( 0 ); if(frm->pageNumber()==page->pageNumber() ) { return (tmp-frm->innerHeight()-footNoteSize( theFrame )); } } } } if (theFrame->frameSet()->isHeaderOrFooter()) return (tmp-footNoteSize( theFrame )); return tmp; } void KWTextFrameSet::frameResized( KWFrame *theFrame, bool invalidateLayout ) { kdDebug(32002) << "KWTextFrameSet::frameResized " << theFrame << " " << *theFrame << " invalidateLayout=" << invalidateLayout << endl; if ( theFrame->height() < 0 ) return; // safety! KWFrameSet * fs = theFrame->frameSet(); Q_ASSERT( fs == this ); fs->updateFrames(); // update e.g. available height Q_ASSERT(frame(0) && frame(0)->frameStack()); frame(0)->frameStack()->update(); theFrame->updateRulerHandles(); // Do a full KWFrameLayout if this will have influence on other frames, i.e.: // * if we resized the last main text frame (the one before the first endnote) // * if we resized an endnote // Delay it though, to get the full height first. if ( fs->isMainFrameset() || fs->isEndNote() ) m_doc->delayedRecalcFrames( theFrame->pageNumber() ); // * if we resized a header, footer, or footnote else if ( fs->frameSetInfo() != KWFrameSet::FI_BODY ) m_doc->recalcFrames( theFrame->pageNumber(), -1 ); // warning this can delete theFrame! // m_doc->frameChanged( theFrame ); // Warning, can't call layout() (frameChanged calls it) // from here, since it calls formatMore() ! if ( invalidateLayout ) m_doc->invalidate(this); // Can't repaint directly, we might be in a paint event already m_doc->delayedRepaintAllViews(); } bool KWTextFrameSet::isFrameEmpty( KWFrame * theFrame ) { KoTextParag * lastParag = textDocument()->lastParag(); // The problem is that if we format things here, and don't emit afterFormatting, // we won't resize autoresize frames properly etc. (e.g. endnotes) // Testcase for this problem: werner's footnote-1.doc //ensureFormatted( lastParag, false ); // maybe true here would do too? slow if maintextframeset though. if ( !lastParag->isValid() ) return false; // we don't know yet int bottom = lastParag->rect().top() + lastParag->rect().height(); if ( theFrame->frameSet() == this ) // safety check { kdDebug() << "KWTextFrameSet::isFrameEmpty text bottom=(LU) " << bottom << " theFrame=" << theFrame << " " << *theFrame << " its internalY(LU)=" << m_doc->ptToLayoutUnitPixY( theFrame->internalY() ) << endl; return bottom < m_doc->ptToLayoutUnitPixY( theFrame->internalY() ); } kdWarning() << "KWTextFrameSet::isFrameEmpty called for frame " << theFrame << " which isn't a child of ours!" << endl; if ( theFrame->frameSet()!=0L && theFrame->frameSet()->name()!=0L) kdDebug() << "(this is " << name() << " and the frame belongs to " << theFrame->frameSet()->name() << ")" << endl; return false; } bool KWTextFrameSet::canRemovePage( int num ) { kdDebug() << "KWTextFrameSet(" << name() << ")::canRemovePage " << num << endl; // No frame on that page ? ok for us then if ( num < m_firstPage || num >= (int)m_framesInPage.size() + m_firstPage ) { kdDebug() << "No frame on that page. Number of frames: " << frameCount() << endl; return true; } TQPtrListIterator frameIt( framesInPage( num ) ); for ( ; frameIt.current(); ++frameIt ) { KWFrame * theFrame = frameIt.current(); kdDebug() << "canRemovePage: looking at " << theFrame << " pageNum=" << theFrame->pageNumber() << endl; Q_ASSERT( theFrame->pageNumber() == num ); Q_ASSERT( theFrame->frameSet() == this ); bool isEmpty = isFrameEmpty( theFrame ); kdDebug() << "KWTextFrameSet(" << name() << ")::canRemovePage" << " found a frame on page " << num << " empty:" << isEmpty << endl; // Ok, so we have a frame on that page -> we can't remove it unless it's a copied frame OR it's empty bool isCopy = theFrame->isCopy() && frameIt.current() != m_frames.first(); if ( !isCopy && !isEmpty ) return false; } return true; } void KWTextFrameSet::deleteFrame( unsigned int num, bool remove, bool recalc ) { KWFrame *frm = m_frames.at( num ); kdDebug() << "KWTextFrameSet(" << name() << ")::deleteFrame " << frm << " (" << num << ")" << endl; if ( frm ) emit frameDeleted( frm ); KWFrameSet::deleteFrame( num, remove, recalc ); } void KWTextFrameSet::updateViewArea( TQWidget * w, KWViewMode* viewMode, const TQPoint & nPointBottom ) { if (!isVisible(viewMode)) return; int ah = availableHeight(); // make sure that it's not -1 #ifdef DEBUG_VIEWAREA kdDebug(32002) << "KWTextFrameSet::updateViewArea " << (void*)w << " " << w->name() << " nPointBottom=" << nPointBottom.x() << "," << nPointBottom.y() << " availHeight=" << ah << " textDocument()->height()=" << textDocument()->height() << endl; #endif // Find last page that is visible int maxPage = m_doc->pageManager()->pageNumber(m_doc->unzoomItY( nPointBottom.y() )); int maxY = 0; if ( maxPage < m_firstPage || maxPage >= (int)m_framesInPage.size() + m_firstPage ) maxY = ah; else { // Find frames on that page, and keep the max bottom, in internal coordinates TQPtrListIterator frameIt( framesInPage( maxPage ) ); for ( ; frameIt.current(); ++frameIt ) { maxY = TQMAX( maxY, m_doc->ptToLayoutUnitPixY( frameIt.current()->internalY() + frameIt.current()->innerHeight() ) ); } } #ifdef DEBUG_VIEWAREA kdDebug(32002) << "KWTextFrameSet (" << name() << ")::updateViewArea maxY now " << maxY << endl; #endif m_textobj->setViewArea( w, maxY ); m_textobj->formatMore( 2 ); } KCommand * KWTextFrameSet::setPageBreakingCommand( KoTextCursor * cursor, int pageBreaking ) { if ( !textDocument()->hasSelection( KoTextDocument::Standard ) && static_cast(cursor->parag())->pageBreaking() == pageBreaking ) return 0L; // No change needed. m_textobj->emitHideCursor(); m_textobj->storeParagUndoRedoInfo( cursor ); if ( !textDocument()->hasSelection( KoTextDocument::Standard ) ) { KWTextParag *parag = static_cast( cursor->parag() ); parag->setPageBreaking( pageBreaking ); m_textobj->setLastFormattedParag( cursor->parag() ); } else { KoTextParag *start = textDocument()->selectionStart( KoTextDocument::Standard ); KoTextParag *end = textDocument()->selectionEnd( KoTextDocument::Standard ); m_textobj->setLastFormattedParag( start ); for ( ; start && start != end->next() ; start = start->next() ) static_cast(start)->setPageBreaking( pageBreaking ); } m_textobj->formatMore( 2 ); emit repaintChanged( this ); KoTextObject::UndoRedoInfo & undoRedoInfo = m_textobj->undoRedoInfoStruct(); undoRedoInfo.newParagLayout.pageBreaking = pageBreaking; KoTextParagCommand *cmd = new KoTextParagCommand( textDocument(), undoRedoInfo.id, undoRedoInfo.eid, undoRedoInfo.oldParagLayouts, undoRedoInfo.newParagLayout, KoParagLayout::PageBreaking ); textDocument()->addCommand( cmd ); undoRedoInfo.clear(); m_textobj->emitShowCursor(); m_textobj->emitUpdateUI( true ); m_textobj->emitEnsureCursorVisible(); // ## find a better name for the command return new KoTextCommand( m_textobj, /*cmd, */i18n("Change Paragraph Attribute") ); } KCommand * KWTextFrameSet::pasteOasis( KoTextCursor * cursor, const TQByteArray & data, bool removeSelected ) { if (protectContent() ) return 0; kdDebug(32001) << "KWTextFrameSet::pasteOasis data:" << data.size() << " bytes" << endl; KMacroCommand * macroCmd = new KMacroCommand( i18n("Paste") ); if ( removeSelected && textDocument()->hasSelection( KoTextDocument::Standard ) ) macroCmd->addCommand( m_textobj->removeSelectedTextCommand( cursor, KoTextDocument::Standard ) ); m_textobj->emitHideCursor(); m_textobj->setLastFormattedParag( cursor->parag()->prev() ? cursor->parag()->prev() : cursor->parag() ); KWOasisPasteCommand * cmd = new KWOasisPasteCommand( textDocument(), cursor->parag()->paragId(), cursor->index(), data ); textDocument()->addCommand( cmd ); macroCmd->addCommand( new KoTextCommand( m_textobj, /*cmd, */TQString() ) ); *cursor = *( cmd->execute( cursor ) ); // not enough when pasting many pages. We need the cursor's parag to be formatted. //m_textobj->formatMore( 2 ); ensureFormatted( cursor->parag() ); emit repaintChanged( this ); m_textobj->emitEnsureCursorVisible(); m_textobj->emitUpdateUI( true ); m_textobj->emitShowCursor(); m_textobj->selectionChangedNotify(); return macroCmd; } void KWTextFrameSet::insertTOC( KoTextCursor * cursor ) { m_textobj->emitHideCursor(); KMacroCommand * macroCmd = new KMacroCommand( i18n("Insert Table of Contents") ); // Remove old TOC KoTextCursor *cur= KWInsertTOCCommand::removeTOC( this, cursor, macroCmd ); // Insert new TOC KoTextDocCommand * cmd = new KWInsertTOCCommand( this,cur ? cur->parag(): cursor->parag() ); textDocument()->addCommand( cmd ); macroCmd->addCommand( new KoTextCommand( m_textobj, TQString() ) ); *cursor = *( cmd->execute( cursor ) ); m_textobj->setLastFormattedParag( textDocument()->firstParag() ); m_textobj->formatMore( 2 ); emit repaintChanged( this ); m_textobj->emitEnsureCursorVisible(); m_textobj->emitUpdateUI( true ); m_textobj->emitShowCursor(); m_doc->addCommand( macroCmd ); } KNamedCommand* KWTextFrameSet::insertFrameBreakCommand( KoTextCursor *cursor ) { KMacroCommand* macroCmd = new KMacroCommand( TQString() ); macroCmd->addCommand( m_textobj->insertParagraphCommand( cursor ) ); KWTextParag *parag = static_cast( cursor->parag() ); if(parag->prev()) { parag=static_cast (parag->prev()); cursor->setParag( parag ); cursor->setIndex( parag->length() - 1 ); } macroCmd->addCommand( setPageBreakingCommand( cursor, parag->pageBreaking() | KoParagLayout::HardFrameBreakAfter ) ); Q_ASSERT( parag->next() ); if ( parag->next() ) { cursor->setParag( parag->next() ); cursor->setIndex( 0 ); } return macroCmd; } void KWTextFrameSet::insertFrameBreak( KoTextCursor *cursor ) { clearUndoRedoInfo(); m_textobj->emitHideCursor(); KNamedCommand* cmd = insertFrameBreakCommand( cursor ); cmd->setName( i18n( "Insert Break After Paragraph" ) ); m_doc->addCommand( cmd ); m_textobj->setLastFormattedParag( cursor->parag() ); m_textobj->formatMore( 2 ); emit repaintChanged( this ); m_textobj->emitEnsureCursorVisible(); m_textobj->emitUpdateUI( true ); m_textobj->emitShowCursor(); } TQRect KWTextFrameSet::paragRect( KoTextParag * parag ) const { // ## Warning. Imagine a paragraph cut in two pieces (at the line-level), // between two columns. A single rect in internal coords, but two rects in // normal coords. TQRect( topLeft, bottomRight ) is just plain wrong. // Currently this method is only used for "ensure visible" so that's fine, but // we shouldn't use it for more precise stuff. KoPoint p; (void)internalToDocument( parag->rect().topLeft(), p ); TQPoint topLeft = m_doc->zoomPoint( p ); (void)internalToDocument( parag->rect().bottomRight(), p ); TQPoint bottomRight = m_doc->zoomPoint( p ); return TQRect( topLeft, bottomRight ); } void KWTextFrameSet::findPosition( const KoPoint &dPoint, KoTextParag * & parag, int & index ) { KoTextCursor cursor( textDocument() ); TQPoint iPoint; if ( documentToInternal( dPoint, iPoint ) ) { cursor.place( iPoint, textDocument()->firstParag() ); parag = cursor.parag(); index = cursor.index(); } else { // Not found, maybe under everything ? parag = textDocument()->lastParag(); if ( parag ) index = parag->length() - 1; } } bool KWTextFrameSet::minMaxInternalOnPage( int pageNum, int& topLU, int& bottomLU ) const { TQPtrListIterator frameIt( framesInPage( pageNum ) ); if ( !frameIt.current() ) return false; // Look at all frames in the page, and keep min and max "internalY" positions double topPt = frameIt.current()->internalY(); double bottomPt = topPt + frameIt.current()->height(); for ( ; frameIt.current(); ++frameIt ) { double y = frameIt.current()->internalY(); topPt = TQMIN( topPt, y ); bottomPt = TQMAX( bottomPt, y + frameIt.current()->height() ); } // Convert to layout units topLU = m_doc->ptToLayoutUnitPixY( topPt ); bottomLU = m_doc->ptToLayoutUnitPixY( bottomPt ); return true; } KoTextParag* KWTextFrameSet::paragAtLUPos( int yLU ) const { KoTextParag* parag = textDocument()->firstParag(); for ( ; parag ; parag = parag->next() ) { if ( parag->rect().bottom() >= yLU ) return parag; } return 0L; } KCommand * KWTextFrameSet::deleteAnchoredFrame( KWAnchor * anchor ) { kdDebug() << "KWTextFrameSet::deleteAnchoredFrame anchor->index=" << anchor->index() << endl; Q_ASSERT( anchor ); KoTextCursor c( textDocument() ); c.setParag( anchor->paragraph() ); c.setIndex( anchor->index() ); textDocument()->setSelectionStart( KoTextDocument::Temp, &c ); c.setIndex( anchor->index() + 1 ); textDocument()->setSelectionEnd( KoTextDocument::Temp, &c ); KCommand *cmd = m_textobj->removeSelectedTextCommand( &c, KoTextDocument::Temp ); m_doc->repaintAllViews(); return cmd; } bool KWTextFrameSet::hasSelection() const { return m_textobj->hasSelection(); } TQString KWTextFrameSet::selectedText() const { return m_textobj->selectedText(); } TQString KWTextFrameSet::toPlainText() const { return m_textobj->textDocument()->plainText(); } void KWTextFrameSet::highlightPortion( KoTextParag * parag, int index, int length, KWCanvas * canvas, bool repaint, KDialogBase* dialog ) { Q_ASSERT( isVisible() ); Q_ASSERT( m_textobj->isVisible() ); //kdDebug() << "highlighting in " << name() << " parag=" << parag->paragId() << " index=" << index << " repaint=" << repaint << endl; m_textobj->highlightPortion( parag, index, length, repaint ); if ( repaint ) { // Position the cursor canvas->editTextFrameSet( this, parag, index ); // Ensure text is fully visible TQRect expose = canvas->viewMode()->normalToView( paragRect( parag ) ); canvas->ensureVisible( (expose.left()+expose.right()) / 2, // point = center of the rect (expose.top()+expose.bottom()) / 2, (expose.right()-expose.left()) / 2, // margin = half-width of the rect (expose.bottom()-expose.top()) / 2); if ( dialog ) { //kdDebug() << k_funcinfo << " dialog=" << dialog << " avoiding rect=" << expose << endl; TQRect globalRect( expose ); globalRect.moveTopLeft( canvas->mapToGlobal( globalRect.topLeft() ) ); KDialog::avoidArea( dialog, globalRect ); } } } void KWTextFrameSet::removeHighlight( bool repaint ) { m_textobj->removeHighlight( repaint ); } void KWTextFrameSet::clearUndoRedoInfo() { m_textobj->clearUndoRedoInfo(); } void KWTextFrameSet::applyStyleChange( KoStyleChangeDefMap changed ) { m_textobj->applyStyleChange( changed ); } // KoTextFormatInterface methods KoTextFormat *KWTextFrameSet::currentFormat() const { return m_textobj->currentFormat(); } KCommand *KWTextFrameSet::setChangeCaseOfTextCommand(KoChangeCaseDia::TypeOfCase _type) { KoTextDocument *textdoc = m_textobj->textDocument(); textdoc->selectAll( KoTextDocument::Standard ); KoTextCursor *cursor = new KoTextCursor( textDocument() ); KCommand* cmd = m_textobj->changeCaseOfText(cursor, _type); textdoc->removeSelection( KoTextDocument::Standard ); delete cursor; return cmd; } KCommand *KWTextFrameSet::setFormatCommand( const KoTextFormat * newFormat, int flags, bool zoomFont ) { m_textobj->textDocument()->selectAll( KoTextDocument::Temp ); KCommand *cmd = m_textobj->setFormatCommand( 0L, 0L, newFormat, flags, zoomFont, KoTextDocument::Temp ); m_textobj->textDocument()->removeSelection( KoTextDocument::Temp ); return cmd; } const KoParagLayout * KWTextFrameSet::currentParagLayoutFormat() const { return m_textobj->currentParagLayoutFormat(); } bool KWTextFrameSet::rtl() const { return m_textobj->rtl(); } KCommand *KWTextFrameSet::setParagLayoutFormatCommand( KoParagLayout *newLayout,int flags, int marginIndex) { return m_textobj->setParagLayoutFormatCommand(newLayout, flags, marginIndex); } class KWFootNoteVarList : public TQPtrList< KWFootNoteVariable > { protected: virtual int compareItems(TQPtrCollection::Item a, TQPtrCollection::Item b) { KWFootNoteVariable* vara = ((KWFootNoteVariable *)a); KWFootNoteVariable* varb = ((KWFootNoteVariable *)b); if ( vara->paragraph() == varb->paragraph() ) { // index() is a bit slow. But this is only called when there are // two footnotes in the same paragraph. int indexa = vara->index(); int indexb = varb->index(); return indexa < indexb ? -1 : indexa == indexb ? 0 : 1; } if ( vara->paragraph()->paragId() < varb->paragraph()->paragId() ) return -1; return 1; } }; void KWTextFrameSet::renumberFootNotes( bool repaint ) { KWFootNoteVarList lst; TQPtrListIterator cit( textDocument()->allCustomItems() ); for ( ; cit.current() ; ++cit ) { KWFootNoteVariable *fnv = dynamic_cast( cit.current() ); if (fnv && !fnv->isDeleted() && (fnv->frameSet() && !fnv->frameSet()->isDeleted())) lst.append( fnv ); } lst.sort(); short int footNoteVarNumber = 0; // absolute order number [internal, not saved nor displayed] short int endNoteVarNumber = 0; short int footNoteNumDisplay = 1; // the number being displayed short int endNoteNumDisplay = 1; bool needRepaint = false; TQPtrListIterator< KWFootNoteVariable > vit( lst ); //create a list with all manual footnotes numbers TQValueList addedNums; for ( ; vit.current() ; ++vit ) { KWFootNoteVariable* var = vit.current(); if ( var->numberingType()==KWFootNoteVariable::Manual ) { uint const num = var->text().toUInt(); if ( num != 0 ) addedNums.append( num ); } } for ( vit.toFirst() ; vit.current() ; ) { KWFootNoteVariable* var = vit.current(); bool endNote = var->noteType() == EndNote; short int & varNumber = endNote ? endNoteVarNumber : footNoteVarNumber; short int & numDisplay = endNote ? endNoteNumDisplay : footNoteNumDisplay; ++varNumber; bool changed = false; if ( varNumber != var->num() || var->numberingType()==KWFootNoteVariable::Manual ) { changed = true; var->setNum( varNumber ); } if ( var->numberingType()==KWFootNoteVariable::Auto ) { if ( addedNums.contains( numDisplay ) != 0 ) // the automatic generated number should not be equal to a manual one { numDisplay++; continue; //try with the next number } if ( numDisplay != var->numDisplay() ) { changed = true; var->setNumDisplay( numDisplay ); } numDisplay++; } if ( changed ) { if ( var->frameSet() ) //safety { TQString fsName = endNote ? i18n("Endnote %1") : i18n("Footnote %1"); if ( var->numberingType()== KWFootNoteVariable::Manual) var->frameSet()->setName( m_doc->generateFramesetName(fsName)); else var->frameSet()->setName( fsName.arg( var->text() ) ); var->frameSet()->setCounterText( var->text() ); } var->resize(); var->paragraph()->invalidate(0); var->paragraph()->setChanged( true ); needRepaint = true; } ++vit; } if ( needRepaint && repaint ) m_doc->slotRepaintChanged( this ); } KoTextDocCommand *KWTextFrameSet::deleteTextCommand( KoTextDocument *textdoc, int id, int index, const TQMemArray & str, const CustomItemsMap & customItemsMap, const TQValueList & oldParagLayouts ) { return new KWTextDeleteCommand( textdoc, id, index, str, customItemsMap, oldParagLayouts ); } TQByteArray KWTextFrameSet::sortText(SortType type) const { const KoTextCursor c1 = textDocument()->selectionStartCursor(KoTextDocument::Standard ); const KoTextCursor c2 = textDocument()->selectionEndCursor( KoTextDocument::Standard ); if ( c1.parag() == c2.parag() ) return TQByteArray(); else { // ( paragraph text -> paragraph ) map. Note that this sorts on the key automatically. TQMap sortMap; sortMap.insert( c1.parag()->toString(0), c1.parag() ); const KoTextParag *p = c1.parag()->next(); while ( p && p != c2.parag() ) { sortMap.insert( p->toString(0), p ); p = p->next(); } sortMap.insert( c2.parag()->toString(0), c2.parag()); typedef TQValueList ParagList; ParagList sortedParags = sortMap.values(); if ( type == KW_SORTDECREASE ) { // I could use an STL algorithm here, but only if TQt was compiled with STL support... ParagList newList; for ( ParagList::const_iterator it = sortedParags.begin(), end = sortedParags.end(); it != end ; ++it ) { newList.prepend( *it ); } sortedParags = newList; } KWOasisSaver oasisSaver( m_doc ); oasisSaver.saveParagraphs( sortedParags ); if ( !oasisSaver.finish() ) return TQByteArray(); return oasisSaver.data(); } } // This is used when loading (KWTextDocument::loadOasisFootnote) // and when inserting from the GUI (KWTextFrameSetEdit::insertFootNote), // so don't add any 'repaint' or 'recalc' code here KWFootNoteFrameSet * KWTextFrameSet::insertFootNote( NoteType noteType, KWFootNoteVariable::Numbering numType, const TQString &manualString ) { kdDebug() << "KWTextFrameSetEdit::insertFootNote " << endl; KWDocument * doc = m_doc; KWFootNoteVariable * var = new KWFootNoteVariable( textDocument(), doc->variableFormatCollection()->format( "NUMBER" ), doc->variableCollection(), doc ); var->setNoteType( noteType ); var->setNumberingType( numType ); if ( numType == KWFootNoteVariable::Manual ) var->setManualString( manualString ); // Now create text frameset which will hold the variable's contents KWFootNoteFrameSet *fs = new KWFootNoteFrameSet( doc, i18n( "Footnotes" ) ); fs->setFrameSetInfo( KWFrameSet::FI_FOOTNOTE ); doc->addFrameSet( fs ); // Bind the footnote variable and its text frameset var->setFrameSet( fs ); fs->setFootNoteVariable( var ); return fs; } KoVariable* KWTextFrameSet::variableUnderMouse( const KoPoint& dPoint ) { TQPoint iPoint; if ( documentToInternal( dPoint, iPoint ) ) return textObject()->variableAtPoint( iPoint ); return 0; } KoLinkVariable* KWTextFrameSet::linkVariableUnderMouse( const KoPoint& dPoint ) { TQPoint iPoint; if ( documentToInternal( dPoint, iPoint ) ) { KoLinkVariable* linkVariable = dynamic_cast( textObject()->variableAtPoint( iPoint ) ); return linkVariable; } return 0; } /////////////////////////////////////////////////////////////////////////////// KWTextFrameSetEdit::KWTextFrameSetEdit( KWTextFrameSet * fs, KWCanvas * canvas ) : KoTextView( fs->textObject() ), KWFrameSetEdit( fs, canvas ), m_rtl( false ) { setBackSpeller( fs->kWordDocument()->backSpeller() ); //kdDebug(32001) << "KWTextFrameSetEdit::KWTextFrameSetEdit " << fs->name() << endl; KoTextView::setReadWrite( fs->kWordDocument()->isReadWrite() ); KoTextObject* textobj = fs->textObject(); connect( textobj, TQ_SIGNAL( selectionChanged(bool) ), canvas, TQ_SIGNAL( selectionChanged(bool) ) ); connect( fs, TQ_SIGNAL( frameDeleted(KWFrame *) ), this, TQ_SLOT( slotFrameDeleted(KWFrame *) ) ); connect( textView(), TQ_SIGNAL( cut() ), TQ_SLOT( cut() ) ); connect( textView(), TQ_SIGNAL( copy() ), TQ_SLOT( copy() ) ); connect( textView(), TQ_SIGNAL( paste() ), TQ_SLOT( paste() ) ); updateUI( true, true ); if( canvas->gui() && canvas->gui()->getHorzRuler()) { if ( !textobj->protectContent() ) canvas->gui()->getHorzRuler()->changeFlags(KoRuler::F_INDENTS | KoRuler::F_TABS); else canvas->gui()->getHorzRuler()->changeFlags(0); } setOverwriteMode( canvas->overwriteMode() ); } KWTextFrameSetEdit::~KWTextFrameSetEdit() { //kdDebug(32001) << "KWTextFrameSetEdit::~KWTextFrameSetEdit" << endl; //m_canvas->gui()->getHorzRuler()->changeFlags(0); } KoTextViewIface* KWTextFrameSetEdit::dcopObject() { if ( !dcop ) dcop = new KWordTextFrameSetEditIface( this ); return dcop; } void KWTextFrameSetEdit::terminate(bool removeSelection) { disconnect( textView()->textObject(), TQ_SIGNAL( selectionChanged(bool) ), m_canvas, TQ_SIGNAL( selectionChanged(bool) ) ); textView()->terminate(removeSelection); } void KWTextFrameSetEdit::slotFrameDeleted( KWFrame *frm ) { if ( m_currentFrame == frm ) m_currentFrame = 0L; } void KWTextFrameSetEdit::paste() { TQMimeSource *data = TQApplication::clipboard()->data(); int provides = KWView::checkClipboard( data ); pasteData( data, provides, false ); } void KWTextFrameSetEdit::pasteData( TQMimeSource* data, int provides, bool drop ) { if ( provides & KWView::ProvidesOasis ) { KCommand* cmd = pasteOasisCommand( data ); if ( cmd ) frameSet()->kWordDocument()->addCommand(cmd); } else if ( provides & KWView::ProvidesPlainText ) { // Note: TQClipboard::text() seems to do a better job than encodedData( "text/plain" ) // In particular it handles charsets (in the mimetype). const TQString text = TQApplication::clipboard()->text(); const bool removeSelected = !drop; if ( !text.isEmpty() ) textObject()->pasteText( cursor(), text, currentFormat(), removeSelected ); } else { kdWarning(32002) << "Unhandled case in KWTextFrameSetEdit::pasteData: provides=" << provides << endl; } // be sure that the footnote number didn't got erased KWFootNoteFrameSet *footNote = dynamic_cast(textFrameSet()); if ( footNote ) { KoParagCounter *counter = footNote->textDocument()->firstParag()->counter(); if ( !counter || ( counter->numbering() != KoParagCounter::NUM_FOOTNOTE ) ) footNote->setCounterText( footNote->footNoteVariable()->text() ); frameSet()->kWordDocument()->slotRepaintChanged( frameSet() ); } } KCommand* KWTextFrameSetEdit::pasteOasisCommand( TQMimeSource* data ) { // Find which mimetype it was (could be oasis text, oasis presentation etc.) TQCString returnedTypeMime = KoTextObject::providesOasis( data ); if ( !returnedTypeMime.isEmpty() ) { TQByteArray arr = data->encodedData( returnedTypeMime ); Q_ASSERT( !arr.isEmpty() ); if ( arr.size() ) return textFrameSet()->pasteOasis( cursor(), arr, true ); } return 0; } void KWTextFrameSetEdit::cut() { if ( textDocument()->hasSelection( KoTextDocument::Standard ) ) { copy(); textObject()->removeSelectedText( cursor() ); } } void KWTextFrameSetEdit::copy() { if ( textDocument()->hasSelection( KoTextDocument::Standard ) ) { TQDragObject *drag = newDrag( 0 ); TQApplication::clipboard()->setData( drag ); } } bool KWTextFrameSetEdit::doIgnoreDoubleSpace(KoTextParag * parag, int index,TQChar ch ) { if( textFrameSet()->kWordDocument()->allowAutoFormat()) { KoAutoFormat * autoFormat = textFrameSet()->kWordDocument()->autoFormat(); if( autoFormat ) { return autoFormat->doIgnoreDoubleSpace( parag, index,ch ); } } return false; } void KWTextFrameSetEdit::doAutoFormat( KoTextCursor* cursor, KoTextParag *parag, int index, TQChar ch ) { if( textFrameSet()->kWordDocument()->allowAutoFormat() ) { KoAutoFormat * autoFormat = textFrameSet()->kWordDocument()->autoFormat(); if( autoFormat ) autoFormat->doAutoFormat( cursor, parag, index, ch, textObject()); } } bool KWTextFrameSetEdit::doCompletion( KoTextCursor* cursor, KoTextParag *parag, int index ) { if( textFrameSet()->kWordDocument()->allowAutoFormat() ) { KoAutoFormat * autoFormat = textFrameSet()->kWordDocument()->autoFormat(); if( autoFormat ) return autoFormat->doCompletion( cursor, parag, index, textObject()); } return false; } bool KWTextFrameSetEdit::doToolTipCompletion( KoTextCursor* cursor, KoTextParag *parag, int index, int keyPressed ) { if( textFrameSet()->kWordDocument()->allowAutoFormat() ) { KoAutoFormat * autoFormat = textFrameSet()->kWordDocument()->autoFormat(); if( autoFormat ) return autoFormat->doToolTipCompletion( cursor, parag, index, textObject(), keyPressed); } return false; } void KWTextFrameSetEdit::showToolTipBox(KoTextParag *parag, int index, TQWidget *widget, const TQPoint &pos) { if( textFrameSet()->kWordDocument()->allowAutoFormat() ) { KoAutoFormat * autoFormat = textFrameSet()->kWordDocument()->autoFormat(); if( autoFormat ) autoFormat->showToolTipBox(parag, index, widget, pos); } } void KWTextFrameSetEdit::removeToolTipCompletion() { if( textFrameSet()->kWordDocument()->allowAutoFormat() ) { KoAutoFormat * autoFormat = textFrameSet()->kWordDocument()->autoFormat(); if( autoFormat ) autoFormat->removeToolTipCompletion(); } } void KWTextFrameSetEdit::textIncreaseIndent() { kdDebug(32001) << "Increasing list" << endl; m_canvas->gui()->getView()->textIncreaseIndent(); } bool KWTextFrameSetEdit::textDecreaseIndent() { if (currentLeftMargin()>0) { kdDebug(32001) << "Decreasing list" << endl; m_canvas->gui()->getView()->textDecreaseIndent(); return true; } else return false; } void KWTextFrameSetEdit::startDrag() { textView()->dragStarted(); m_canvas->dragStarted(); TQDragObject *drag = newDrag( m_canvas->viewport() ); if ( !frameSet()->kWordDocument()->isReadWrite() ) drag->dragCopy(); else { bool move = ( drag->drag() ); if ( move ) { #if 0 if ( TQDragObject::target() != m_canvas && TQDragObject::target() != m_canvas->viewport() ) { //This is when dropping text _out_ of KWord. Since we have Move and Copy //options (Copy being accessed by pressing CTRL), both are possible. //But is that intuitive enough ? Doesn't the user expect a Copy in all cases ? //Losing the selected text when dropping out of kword seems quite unexpected to me. //Undecided about this........ textObject()->removeSelectedText( cursor() ); } #endif } } } TQDragObject * KWTextFrameSetEdit::newDrag( TQWidget * parent ) { KWTextFrameSet* fs = textFrameSet(); return fs->kWordDocument()->dragSelected( parent, fs ); } void KWTextFrameSetEdit::ensureCursorVisible() { //kdDebug() << "KWTextFrameSetEdit::ensureCursorVisible paragId=" << cursor()->parag()->paragId() << " cursor->index()=" << cursor()->index() << endl; KoTextParag * parag = cursor()->parag(); int idx = cursor()->index(); textFrameSet()->ensureFormatted( parag ); KoTextStringChar *chr = parag->at( idx ); int cursorHeight = parag->lineHeightOfChar( idx ); int x = parag->rect().x() + cursor()->x(); // this includes +charwidth for an RTL char //kdDebug() << "parag->rect().x()=" << parag->rect().x() << " x=" << cursor()->x() << endl; int y = 0; int dummy; parag->lineHeightOfChar( idx, &dummy, &y ); y += parag->rect().y(); //kdDebug() << "KWTextFrameSetEdit::ensureCursorVisible y=" << y << endl; // make sure one char is visible before, and one after KoTextStringChar *chrLeft = idx > 0 ? chr-1 : chr; // which char is on the left and which one is on the right depends on chr->rightToLeft int areaLeft = chr->rightToLeft ? chr->width : chrLeft->width; int areaRight = chr->rightToLeft ? chrLeft->width : chr->width; KoPoint pt; KoPoint hintDPoint; if ( m_currentFrame ) hintDPoint = m_currentFrame->topLeft(); KWFrame * theFrame = textFrameSet()->internalToDocumentWithHint( TQPoint(x, y), pt, hintDPoint ); //kdDebug() << "KWTextFrameSetEdit::ensureCursorVisible frame=" << theFrame << " m_currentFrame=" << m_currentFrame << endl; if ( theFrame && m_currentFrame != theFrame ) { m_currentFrame = theFrame; m_canvas->gui()->getView()->updatePageInfo(); } TQPoint cursorPos = textFrameSet()->kWordDocument()->zoomPoint( pt ); cursorPos = m_canvas->viewMode()->normalToView( cursorPos ); areaLeft = textFrameSet()->kWordDocument()->layoutUnitToPixelX( areaLeft ) + 1; areaRight = textFrameSet()->kWordDocument()->layoutUnitToPixelX( areaRight ) + 1; cursorHeight = textFrameSet()->kWordDocument()->layoutUnitToPixelY( cursorHeight ); //kdDebug() << "KWTextFrameSetEdit::ensureCursorVisible pt=" << pt << " cursorPos=" << cursorPos // << " areaLeft=" << areaLeft << " areaRight=" << areaRight << " y=" << y << endl; m_canvas->ensureVisible( cursorPos.x() - areaLeft, cursorPos.y() + cursorHeight / 2, areaLeft + areaRight, cursorHeight / 2 + 2 ); } bool KWTextFrameSetEdit::enterCustomItem( KoTextCustomItem* customItem, bool fromRight ) { KWAnchor* anchor = dynamic_cast( customItem ); if ( anchor ) { KWFrameSet* frameSet = anchor->frameSet(); if ( frameSet->type() == FT_FORMULA || frameSet->type() == FT_TEXT ) { // store the instance variable we need after "delete this" KWCanvas* canvas = m_canvas; // this will "delete this"! m_canvas->editFrameSet( frameSet ); // We assume that `editFrameSet' succeeded. if ( fromRight ) { KWFrameSetEdit* edit = canvas->currentFrameSetEdit(); if ( frameSet->type() == FT_FORMULA ) static_cast( edit )->moveEnd(); else static_cast( edit )->moveCursor( MoveEnd ); } if ( frameSet->type() == FT_FORMULA ) { // A FormulaFrameSetEdit looks a little different from // a FormulaFrameSet. (Colors) static_cast( frameSet )->setChanged(); canvas->repaintChanged( frameSet, true ); } return true; } } return false; } void KWTextFrameSetEdit::keyPressEvent( TQKeyEvent* e ) { // Handle moving into inline frames (e.g. formula frames). if ( !( e->state() & ControlButton ) && !( e->state() & ShiftButton ) ) { if (e->state() != TQt::NoButton) removeToolTipCompletion(); switch ( e->key() ) { case Key_Left: { KoTextCursor* cursor = textView()->cursor(); KoTextParag* parag = cursor->parag(); int index = cursor->index(); if ( index > 0 ) { KoTextStringChar* ch = parag->at( index-1 ); if ( ch->isCustom() ) { KoTextCustomItem* customItem = ch->customItem(); if ( enterCustomItem( customItem, true ) ) { // Don't do anything here, "this" is deleted! return; } } } if ( index == 0 && !parag->prev() ) if ( exitLeft() ) return; break; } case Key_Right: { KoTextCursor* cursor = textView()->cursor(); KoTextParag* parag = cursor->parag(); int index = cursor->index(); if ( index < parag->length() - 1 ) { KoTextStringChar* ch = parag->at( index ); if ( ch->isCustom() ) { KoTextCustomItem* customItem = ch->customItem(); if ( enterCustomItem( customItem, false ) ) { // Don't do anything here, "this" is deleted! return; } } } else if ( /*at end, covered by previous if, && */ !parag->next() ) if ( exitRight() ) return; break; } } } // Calculate position of tooltip for autocompletion TQPoint pos = textFrameSet()->cursorPos( cursor(), m_canvas, m_currentFrame ); textView()->handleKeyPressEvent( e, m_canvas, pos ); } void KWTextFrameSetEdit::keyReleaseEvent( TQKeyEvent* e ) { textView()->handleKeyReleaseEvent( e ); } void KWTextFrameSetEdit::imStartEvent( TQIMEvent* e ) { textView()->handleImStartEvent( e ); } void KWTextFrameSetEdit::imComposeEvent( TQIMEvent* e ) { textView()->handleImComposeEvent( e ); } void KWTextFrameSetEdit::imEndEvent( TQIMEvent* e ) { textView()->handleImEndEvent( e ); } void KWTextFrameSetEdit::mousePressEvent( TQMouseEvent *e, const TQPoint &, const KoPoint & dPoint ) { if ( dPoint.x() < 0 || dPoint.y() < 0 ) return; // Ignore clicks completely outside of the page (e.g. in the gray area, or ruler) textFrameSet()->textObject()->clearUndoRedoInfo(); if ( m_currentFrame ) hideCursor(); // Need to do that with the old m_currentFrame TQPoint iPoint; KWTextFrameSet::RelativePosition relPos; KWFrame * theFrame = textFrameSet()->documentToInternalMouseSelection( dPoint, iPoint, relPos, m_canvas->viewMode() ); if ( theFrame && m_currentFrame != theFrame ) { m_currentFrame = theFrame; m_canvas->gui()->getView()->updatePageInfo(); } if ( m_currentFrame ) { // Let KoTextView handle the mousepress event - but don't let it start // a drag if clicking on the left of the text (out of the frame itself) bool addParag = textView()->handleMousePressEvent( e, iPoint, relPos != KWTextFrameSet::LeftOfFrame, frameSet()->kWordDocument()->insertDirectCursor() ); // Clicked on the left of the text -> select the whole paragraph if ( relPos == KWTextFrameSet::LeftOfFrame ) textView()->selectParagUnderCursor( *textView()->cursor() ); if ( addParag ) frameSet()->kWordDocument()->setModified(true ); } // else mightStartDrag = FALSE; necessary? if ( e->button() != TQt::LeftButton ) return; KoVariable* var = variable(); if ( var ) { KWFootNoteVariable * footNoteVar = dynamic_cast( var ); if ( footNoteVar ) { footNoteVar->frameSet()->startEditing( m_canvas ); // --- and now we are deleted! --- } } } void KWTextFrameSetEdit::mouseMoveEvent( TQMouseEvent * e, const TQPoint & nPoint, const KoPoint & ) { if ( textView()->maybeStartDrag( e ) ) return; if ( nPoint.x() < 0 || nPoint.y() < 0 ) return; // Ignore clicks completely outside of the page (e.g. in the gray area, or ruler) TQPoint iPoint; KoPoint dPoint = frameSet()->kWordDocument()->unzoomPoint( nPoint ); KWTextFrameSet::RelativePosition relPos; if ( nPoint.y() > 0 && textFrameSet()->documentToInternalMouseSelection( dPoint, iPoint, relPos , m_canvas->viewMode()) ) { if ( relPos == KWTextFrameSet::LeftOfFrame ) textView()->extendParagraphSelection( iPoint ); else textView()->handleMouseMoveEvent( e, iPoint ); } } bool KWTextFrameSetEdit::openLink( KoLinkVariable* variable ) { KWTextFrameSet* fs = textFrameSet(); KWDocument* doc = fs->kWordDocument(); if ( doc->variableCollection()->variableSetting()->displayLink() ) { const TQString url = variable->url(); if( url.startsWith("bkm://") ) { const KoTextBookmark* bookmark = doc->bookmarkByName(url.mid(6) ); if ( bookmark ) { cursor()->setParag( bookmark->startParag() ); ensureCursorVisible(); return true; } } KoTextView::openLink( variable ); return true; } return false; } void KWTextFrameSetEdit::openLink() { KoLinkVariable* v = linkVariable(); if ( v ) openLink( v ); } void KWTextFrameSetEdit::mouseReleaseEvent( TQMouseEvent *, const TQPoint &, const KoPoint & ) { textView()->handleMouseReleaseEvent(); } void KWTextFrameSetEdit::mouseDoubleClickEvent( TQMouseEvent *e, const TQPoint &, const KoPoint & ) { textView()->handleMouseDoubleClickEvent( e, TQPoint() /* Currently unused */ ); } void KWTextFrameSetEdit::dragEnterEvent( TQDragEnterEvent * e ) { int provides = KWView::checkClipboard( e ); if ( !frameSet()->kWordDocument()->isReadWrite() || provides == 0 ) { e->ignore(); return; } e->acceptAction(); } void KWTextFrameSetEdit::dragMoveEvent( TQDragMoveEvent * e, const TQPoint &nPoint, const KoPoint & ) { int provides = KWView::checkClipboard( e ); if ( !frameSet()->kWordDocument()->isReadWrite() || provides == 0 ) { e->ignore(); return; } // place cursor - unless dropping an image. well it's hard to know if the user // wants the dropped image to be inline or absolute positioned. if ( provides & ( KWView::ProvidesOasis | KWView::ProvidesPlainText | KWView::ProvidesFormula ) ) { TQPoint iPoint; KoPoint dPoint = frameSet()->kWordDocument()->unzoomPoint( nPoint ); if ( textFrameSet()->documentToInternal( dPoint, iPoint ) ) { textObject()->emitHideCursor(); placeCursor( iPoint ); textObject()->emitShowCursor(); } } e->acceptAction(); } void KWTextFrameSetEdit::dragLeaveEvent( TQDragLeaveEvent * ) { } void KWTextFrameSetEdit::dropEvent( TQDropEvent * e, const TQPoint & nPoint, const KoPoint &, KWView* view ) { int provides = KWView::checkClipboard( e ); if ( frameSet()->kWordDocument()->isReadWrite() && provides ) { e->acceptAction(); KoTextCursor dropCursor( textDocument() ); TQPoint dropPoint; KoPoint dPoint = frameSet()->kWordDocument()->unzoomPoint( nPoint ); if ( !textFrameSet()->documentToInternal( dPoint, dropPoint ) ) return; // Don't know where to paste dropCursor.place( dropPoint, textDocument()->firstParag() ); kdDebug(32001) << "KWTextFrameSetEdit::dropEvent dropCursor at parag=" << dropCursor.parag()->paragId() << " index=" << dropCursor.index() << endl; if ( ( e->source() == m_canvas || e->source() == m_canvas->viewport() ) && e->action() == TQDropEvent::Move && // this is the indicator that the source and dest text objects are the same textDocument()->hasSelection( KoTextDocument::Standard ) ) { KCommand *cmd = textView()->prepareDropMove( dropCursor ); if(cmd) { KMacroCommand* macroCmd = new KMacroCommand( i18n( "Move Text" ) ); macroCmd->addCommand(cmd); cmd = pasteOasisCommand( e ); if ( cmd ) macroCmd->addCommand(cmd); //relayout textframeset after a dnd otherwise autoextend //frameset is not re-layouted textFrameSet()->layout(); frameSet()->kWordDocument()->addCommand( macroCmd ); } return; } else { // drop coming from outside -> forget about current selection textDocument()->removeSelection( KoTextDocument::Standard ); textObject()->selectionChangedNotify(); } // The cursor is already correctly positioned, all we need to do is to "paste" the dropped data. view->pasteData( e, true ); } } void KWTextFrameSetEdit::focusInEvent() { textView()->focusInEvent(); } void KWTextFrameSetEdit::focusOutEvent() { textView()->focusOutEvent(); } void KWTextFrameSetEdit::selectAll() { textObject()->selectAll( true ); } void KWTextFrameSetEdit::drawCursor( bool visible ) { #ifdef DEBUG_CURSOR kdDebug() << "KWTextFrameSetEdit::drawCursor " << visible << endl; #endif KoTextView::drawCursor( visible ); if ( !cursor()->parag() ) return; if ( !cursor()->parag()->isValid() ) textFrameSet()->ensureFormatted( cursor()->parag() ); if ( !frameSet()->kWordDocument()->isReadWrite() ) return; if ( m_canvas->viewMode()->hasFrames() && !m_currentFrame ) return; TQPainter p( m_canvas->viewport() ); p.translate( -m_canvas->contentsX(), -m_canvas->contentsY() ); p.setBrushOrigin( -m_canvas->contentsX(), -m_canvas->contentsY() ); textFrameSet()->drawCursor( &p, cursor(), visible, m_canvas, m_currentFrame ); } bool KWTextFrameSetEdit::pgUpKeyPressed() { TQRect crect( m_canvas->contentsX(), m_canvas->contentsY(), m_canvas->visibleWidth(), m_canvas->visibleHeight() ); crect = m_canvas->viewMode()->viewToNormal( crect ); // Go up of 90% of crect.height() int h = frameSet()->kWordDocument()->pixelToLayoutUnitY( (int)( (double)crect.height() * 0.9 ) ); KoTextParag *s = textView()->cursor()->parag(); KoTextParag* oldParag = s; int y = s->rect().y(); while ( s ) { if ( y - s->rect().y() >= h ) break; s = s->prev(); } if ( !s ) s = textDocument()->firstParag(); textView()->cursor()->setParag( s ); textView()->cursor()->setIndex( 0 ); if ( s == oldParag ) { m_canvas->viewportScroll( true ); return false; } return true; } bool KWTextFrameSetEdit::pgDownKeyPressed() { TQRect crect( m_canvas->contentsX(), m_canvas->contentsY(), m_canvas->visibleWidth(), m_canvas->visibleHeight() ); crect = m_canvas->viewMode()->viewToNormal( crect ); // Go down of 90% of crect.height() int h = frameSet()->kWordDocument()->pixelToLayoutUnitY( (int)( (double)crect.height() * 0.9 ) ); KoTextCursor *cursor = textView()->cursor(); KoTextParag *s = cursor->parag(); KoTextParag* oldParag = s; int y = s->rect().y(); while ( s ) { if ( s->rect().y() - y >= h ) break; s = s->next(); } if ( !s ) { s = textDocument()->lastParag(); cursor->setParag( s ); cursor->setIndex( s->length() - 1 ); } else { cursor->setParag( s ); cursor->setIndex( 0 ); } if ( s == oldParag ) { m_canvas->viewportScroll( false ); return false; } return true; } void KWTextFrameSetEdit::ctrlPgUpKeyPressed() { if ( m_currentFrame ) { TQPoint iPoint = textFrameSet()->moveToPage( m_currentFrame->pageNumber(), -1 ); if ( !iPoint.isNull() ) placeCursor( iPoint ); } } void KWTextFrameSetEdit::ctrlPgDownKeyPressed() { if ( m_currentFrame ) { TQPoint iPoint = textFrameSet()->moveToPage( m_currentFrame->pageNumber(), +1 ); if ( !iPoint.isNull() ) placeCursor( iPoint ); } } void KWTextFrameSetEdit::setCursor( KoTextParag* parag, int index ) { cursor()->setParag( parag ); cursor()->setIndex( index ); } void KWTextFrameSetEdit::insertExpression(const TQString &_c) { if(textObject()->hasSelection() ) frameSet()->kWordDocument()->addCommand(textObject()->replaceSelectionCommand( cursor(), _c, i18n("Insert Expression"))); else textObject()->insert( cursor(), currentFormat(), _c, i18n("Insert Expression") ); } void KWTextFrameSetEdit::insertFloatingFrameSet( KWFrameSet * fs, const TQString & commandName ) { textObject()->clearUndoRedoInfo(); CustomItemsMap customItemsMap; TQString placeHolders; // TODO support for multiple floating items (like multiple-page tables) int frameNumber = 0; int index = 0; int insertFlags = KoTextObject::DoNotRemoveSelected; { // the loop will start here :) KWAnchor * anchor = fs->createAnchor( textFrameSet()->textDocument(), frameNumber ); if ( frameNumber == 0 && anchor->ownLine() && cursor()->index() > 0 ) // enforce start of line - currently unused { kdDebug() << "ownline -> prepending \\n" << endl; placeHolders += TQChar('\n'); index++; insertFlags |= KoTextObject::CheckNewLine; } placeHolders += KoTextObject::customItemChar(); customItemsMap.insert( index, anchor ); } fs->setAnchored( textFrameSet() ); textObject()->insert( cursor(), currentFormat(), placeHolders, commandName, KoTextDocument::Standard, insertFlags, customItemsMap ); } void KWTextFrameSetEdit::insertLink(const TQString &_linkName, const TQString & hrefName) { KWDocument * doc = frameSet()->kWordDocument(); KoVariable * var = new KoLinkVariable( textFrameSet()->textDocument(), _linkName, hrefName, doc->variableFormatCollection()->format( "STRING" ), doc->variableCollection() ); insertVariable( var ); } void KWTextFrameSetEdit::insertComment(const TQString &_comment) { KWDocument * doc = frameSet()->kWordDocument(); KoVariable * var = new KoNoteVariable( textFrameSet()->textDocument(), _comment, doc->variableFormatCollection()->format( "STRING" ), doc->variableCollection() ); insertVariable( var ); } void KWTextFrameSetEdit::insertCustomVariable( const TQString &name) { KWDocument * doc = frameSet()->kWordDocument(); KoVariable * var = new KoCustomVariable( textFrameSet()->textDocument(), name, doc->variableFormatCollection()->format( "STRING" ), doc->variableCollection()); insertVariable( var ); } void KWTextFrameSetEdit::insertFootNote( NoteType noteType, KWFootNoteVariable::Numbering numType, const TQString &manualString ) { KWFootNoteFrameSet *fs = textFrameSet()->insertFootNote( noteType, numType, manualString ); KWFootNoteVariable * var = fs->footNoteVariable(); // Place the frame on the correct page, but the exact coordinates // will be determined by recalcFrames (KWFrameLayout) int pageNum = m_currentFrame->pageNumber(); fs->createInitialFrame( pageNum ); insertVariable( var ); // Re-number footnote variables textFrameSet()->renumberFootNotes(); // Layout the footnote frame textFrameSet()->kWordDocument()->recalcFrames( pageNum, -1 ); // we know that for sure nothing changed before this page. //KoTextParag* parag = fs->textDocument()->firstParag(); //parag->truncate(0); // why? we just created it, anyway... // And now edit the footnote frameset - all WPs do that it seems. fs->startEditing( m_canvas ); // --- and now we are deleted! --- } void KWTextFrameSetEdit::insertVariable( int type, int subtype ) { kdDebug() << "KWTextFrameSetEdit::insertVariable " << type << endl; KWDocument * doc = frameSet()->kWordDocument(); KoVariable * var = 0L; bool refreshCustomMenu = false; if ( type == VT_CUSTOM ) { KoCustomVarDialog dia( m_canvas ); if ( dia.exec() == TQDialog::Accepted ) { KoCustomVariable *v = new KoCustomVariable( textFrameSet()->textDocument(), dia.name(), doc->variableFormatCollection()->format( "STRING" ),doc->variableCollection() ); v->setValue( dia.value() ); var = v; refreshCustomMenu = true; } } else if ( type == VT_MAILMERGE ) { KWMailMergeVariableInsertDia dia( m_canvas, doc->mailMergeDataBase() ); if ( dia.exec() == TQDialog::Accepted ) { var = new KWMailMergeVariable( textFrameSet()->textDocument(), dia.getName(), doc->variableFormatCollection()->format( "STRING" ),doc->variableCollection(),doc ); } } else var = doc->variableCollection()->createVariable( type, subtype, doc->variableFormatCollection(), 0L, textFrameSet()->textDocument(), doc, 0); if ( var) insertVariable( var, 0L /*means currentFormat()*/, refreshCustomMenu); } void KWTextFrameSetEdit::insertVariable( KoVariable *var, KoTextFormat *format /*=0*/, bool refreshCustomMenu ) { if ( var ) { CustomItemsMap customItemsMap; customItemsMap.insert( 0, var ); if (!format) format = currentFormat(); kdDebug() << "KWTextFrameSetEdit::insertVariable inserting into paragraph" << endl; #ifdef DEBUG_FORMATS kdDebug() << "KWTextFrameSetEdit::insertVariable format=" << format << endl; #endif textObject()->insert( cursor(), format, KoTextObject::customItemChar(), i18n("Insert Variable"), KoTextDocument::Standard, KoTextObject::DoNotRemoveSelected, customItemsMap ); frameSet()->kWordDocument()->slotRepaintChanged( frameSet() ); if ( var->type()==VT_CUSTOM && refreshCustomMenu) frameSet()->kWordDocument()->refreshMenuCustomVariable(); } } void KWTextFrameSetEdit::insertWPPage() { KWTextFrameSet* textfs = textFrameSet(); textfs->clearUndoRedoInfo(); KoTextObject* textobj = textObject(); KWDocument * doc = frameSet()->kWordDocument(); int pages = doc->pageCount(); int columns = doc->numColumns(); // There could be N columns. In that case we may need to add up to N framebreaks. int inserted = 0; KMacroCommand* macroCmd = new KMacroCommand( i18n("Insert Page") ); do { macroCmd->addCommand( textfs->insertFrameBreakCommand( cursor() ) ); textobj->setLastFormattedParag( cursor()->parag() ); textobj->formatMore( 2 ); } while ( pages == doc->pageCount() && ++inserted <= columns ); if ( pages == doc->pageCount() ) kdWarning(32002) << k_funcinfo << " didn't manage to insert a new page! inserted=" << inserted << " columns=" << columns << " pages=" << pages << endl; doc->addCommand( macroCmd ); textfs->slotRepaintChanged(); textobj->emitEnsureCursorVisible(); textobj->emitUpdateUI( true ); textobj->emitShowCursor(); } KoBorder KWTextFrameSetEdit::border(KoBorder::BorderType type) { if(type == KoBorder::LeftBorder) return m_paragLayout.leftBorder; if(type == KoBorder::RightBorder) return m_paragLayout.rightBorder; if(type == KoBorder::TopBorder) return m_paragLayout.topBorder; return m_paragLayout.bottomBorder; } // Update the GUI toolbar button etc. to reflect the current cursor position. void KWTextFrameSetEdit::updateUI( bool updateFormat, bool force ) { // Update UI - only for those items which have changed KoTextView::updateUI( updateFormat, force ); // Paragraph settings KWTextParag * parag = static_cast(cursor()->parag()); if ( m_paragLayout.alignment != parag->resolveAlignment() || force ) { m_paragLayout.alignment = parag->resolveAlignment(); m_canvas->gui()->getView()->showAlign( m_paragLayout.alignment ); } // Counter if ( !m_paragLayout.counter ) m_paragLayout.counter = new KoParagCounter; // we can afford to always have one here KoParagCounter::Style cstyle = m_paragLayout.counter->style(); if ( parag->counter() ) *m_paragLayout.counter = *parag->counter(); else { m_paragLayout.counter->setNumbering( KoParagCounter::NUM_NONE ); m_paragLayout.counter->setStyle( KoParagCounter::STYLE_NONE ); } if ( m_paragLayout.counter->style() != cstyle || force ) m_canvas->gui()->getView()->showCounter( * m_paragLayout.counter ); if(m_paragLayout.leftBorder!=parag->leftBorder() || m_paragLayout.rightBorder!=parag->rightBorder() || m_paragLayout.topBorder!=parag->topBorder() || m_paragLayout.bottomBorder!=parag->bottomBorder() || force ) { m_paragLayout.leftBorder = parag->leftBorder(); m_paragLayout.rightBorder = parag->rightBorder(); m_paragLayout.topBorder = parag->topBorder(); m_paragLayout.bottomBorder = parag->bottomBorder(); m_canvas->gui()->getView()->updateBorderButtons( m_paragLayout.leftBorder, m_paragLayout.rightBorder, m_paragLayout.topBorder, m_paragLayout.bottomBorder ); } if ( !parag->style() ) kdWarning() << "Paragraph " << parag->paragId() << " has no style" << endl; else if ( m_paragLayout.style != parag->style() || force ) { m_paragLayout.style = parag->style(); m_canvas->gui()->getView()->showStyle( m_paragLayout.style->name() ); } if( m_paragLayout.margins[TQStyleSheetItem::MarginLeft] != parag->margin(TQStyleSheetItem::MarginLeft) || m_paragLayout.margins[TQStyleSheetItem::MarginFirstLine] != parag->margin(TQStyleSheetItem::MarginFirstLine) || m_paragLayout.margins[TQStyleSheetItem::MarginRight] != parag->margin(TQStyleSheetItem::MarginRight) || parag->string()->isRightToLeft() != m_rtl || force ) { m_paragLayout.margins[TQStyleSheetItem::MarginFirstLine] = parag->margin(TQStyleSheetItem::MarginFirstLine); m_paragLayout.margins[TQStyleSheetItem::MarginLeft] = parag->margin(TQStyleSheetItem::MarginLeft); m_paragLayout.margins[TQStyleSheetItem::MarginRight] = parag->margin(TQStyleSheetItem::MarginRight); if ( m_rtl != parag->string()->isRightToLeft() && parag->counter() ) { parag->counter()->invalidate(); parag->setChanged( true ); // repaint } m_rtl = parag->string()->isRightToLeft(); m_canvas->gui()->getView()->showRulerIndent( m_paragLayout.margins[TQStyleSheetItem::MarginLeft], m_paragLayout.margins[TQStyleSheetItem::MarginFirstLine], m_paragLayout.margins[TQStyleSheetItem::MarginRight], m_rtl ); } if( m_paragLayout.tabList() != parag->tabList() || force) { m_paragLayout.setTabList( parag->tabList() ); KoRuler * hr = m_canvas->gui()->getHorzRuler(); if ( hr ) hr->setTabList( parag->tabList() ); } if( m_paragLayout.lineSpacingType != parag->paragLayout().lineSpacingType || force) { m_paragLayout.lineSpacingType = parag->paragLayout().lineSpacingType; m_canvas->gui()->getView()->showSpacing( m_paragLayout.lineSpacingType ); } // There are more paragraph settings, but those that are not directly // visible in the UI don't need to be handled here. // For instance parag stuff, borders etc. } void KWTextFrameSetEdit::showFormat( KoTextFormat *format ) { m_canvas->gui()->getView()->showFormat( *format ); } TQPoint KWTextFrameSet::cursorPos( KoTextCursor *cursor, KWCanvas* canvas, KWFrame* currentFrame ) { KWViewMode *viewMode = canvas->viewMode(); KoTextParag* parag = cursor->parag(); const TQPoint topLeft = parag->rect().topLeft(); // in TQRT coords int lineY; parag->lineHeightOfChar( cursor->index(), 0, &lineY ); // iPoint is the topright corner of the current character TQPoint iPoint( topLeft.x() + cursor->x() + parag->at( cursor->index() )->width, topLeft.y() + lineY ); KoPoint dPoint; TQPoint vPoint; KoPoint hintDPoint = currentFrame ? currentFrame->innerRect().topLeft() : KoPoint(); if ( internalToDocumentWithHint( iPoint, dPoint, hintDPoint ) ) { vPoint = viewMode->normalToView( m_doc->zoomPoint( dPoint ) ); // from doc to view contents vPoint.rx() -= canvas->contentsX(); vPoint.ry() -= canvas->contentsY(); } // else ... ? return vPoint; } ////// bool KWFootNoteFrameSet::isFootNote() const { if ( !m_footNoteVar ) { kdWarning() << k_funcinfo << " called too early? No footnote var." << endl; return false; } return ( m_footNoteVar->noteType() == FootNote ); } bool KWFootNoteFrameSet::isEndNote() const { if ( !m_footNoteVar ) { kdWarning() << k_funcinfo << " called too early? No footnote var." << endl; return false; } return ( m_footNoteVar->noteType() == EndNote ); } void KWFootNoteFrameSet::createInitialFrame( int pageNum ) { KWFrame *frame = new KWFrame(this, 0, m_doc->pageManager()->topOfPage(pageNum) + 1, 20, 20 ); frame->setFrameBehavior(KWFrame::AutoExtendFrame); frame->setNewFrameBehavior(KWFrame::NoFollowup); addFrame( frame ); } void KWFootNoteFrameSet::startEditing( KWCanvas* canvas ) { canvas->editFrameSet( this ); // Ensure cursor is visible KWTextFrameSetEdit *textedit = dynamic_cast(canvas->currentFrameSetEdit()->currentTextEdit()); if ( textedit ) textedit->ensureCursorVisible(); } void KWFootNoteFrameSet::setFootNoteVariable( KWFootNoteVariable* var ) { m_footNoteVar = var; } void KWFootNoteFrameSet::setCounterText( const TQString& text ) { KoTextParag* parag = textDocument()->firstParag(); Q_ASSERT( parag ); if ( parag ) { KoParagCounter counter; counter.setNumbering( KoParagCounter::NUM_FOOTNOTE ); counter.setPrefix( text ); counter.setSuffix( TQString() ); parag->setCounter( counter ); } } KWordFrameSetIface* KWFootNoteFrameSet::dcopObject() { if ( !m_dcop ) m_dcop = new KWFootNoteFrameSetIface( this ); return m_dcop; } #include "KWTextFrameSet.moc"