Subversion Repositories Scribus

Rev

Rev 5778 | Rev 6144 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

//FIXME: this include must go to sctextstruct.h !
#include <qvaluelist.h>
#include <cassert>  //added to make Fedora-5 happy
#include "fpoint.h"
#include "scfonts.h"
//#include "sctextstruct.h"
#include "storytext.h"
#include "scribus.h"
#include "util.h"


QChar SpecialChars::OBJECT     = QChar(25);
QChar SpecialChars::TAB        = QChar(9);
QChar SpecialChars::PARSEP     = QChar(13);
QChar SpecialChars::LINEBREAK  = QChar(28);
QChar SpecialChars::COLBREAK   = QChar(26);
QChar SpecialChars::FRAMEBREAK = QChar(27);
QChar SpecialChars::SHYPHEN;
QChar SpecialChars::NBHYPHEN   = QChar(24);
QChar SpecialChars::NBSPACE    = QChar(29);
QChar SpecialChars::ZWNBSPACE;
QChar SpecialChars::ZWSPACE;
//QChar SpecialChars::SPACE      = QChar(32);

class ScText_Shared : public QPtrList<ScText>
{
public:
        ParagraphStyle defaultStyle;
        uint refs;
        uint len;
        ScText_Shared() : QPtrList<ScText>(), defaultStyle(), refs(1), len(0)
        {
                setAutoDelete(true);
        }
};


StoryText::StoryText(ScribusDoc * doc_) : doc(doc_)
{
        d = new ScText_Shared();
        clear();
}

StoryText::StoryText(const StoryText & other) : doc(other.doc)
{
        d = other.d;
        d->refs++;
       
        selFirst = 0;
        selLast = -1;
       
        firstFrameItem = 0;
        lastFrameItem = -1;

        invalidateLayout();
}

StoryText::~StoryText()
{
        d->refs--;
        if (d->refs == 0) {
                for (ScText *it = d->first(); it != 0; it = d->next())
                {
                        if ((it->ch[0] == SpecialChars::OBJECT) && (it->cembedded != 0)) {
                                doc->FrameItems.remove(it->cembedded);
                                delete it->cembedded;
                                it->cembedded = 0;
                        }
                        else if ((it->ch[0] == SpecialChars::PARSEP)) {
                                delete it->parstyle;
                                it->parstyle = 0;
                        }
                }
               
                d->clear();
                d->len = 0;
                delete d;
        }      
}

StoryText& StoryText::operator= (const StoryText & other)
{
        d->refs--;
        if (d->refs == 0) {
                clear();
                delete d;
        }
       
        doc = other.doc;
        d = other.d;
        d->refs++;
       
        selFirst = 0;
        selLast = -1;
       
        firstFrameItem = 0;
        lastFrameItem = -1;

        invalidateLayout();
        return *this;
}


void StoryText::clear()
{
        selFirst = 0;
        selLast = -1;

        firstFrameItem = 0;
        lastFrameItem = -1;
       
        d->defaultStyle = ParagraphStyle();
        d->defaultStyle.setParent( & doc->docParagraphStyles[0]);
        d->defaultStyle.charStyle().setParent( & doc->docParagraphStyles[0].charStyle());
       
        for (ScText *it = d->first(); it != 0; it = d->next())
        {
                if ((it->ch[0] == SpecialChars::OBJECT) && (it->cembedded != 0)) {
                        doc->FrameItems.remove(it->cembedded);
                        delete it->cembedded;
                        it->cembedded = 0;
                }
                else if ((it->ch[0] == SpecialChars::PARSEP) && it->parstyle) {
                        delete it->parstyle;
                        it->parstyle = 0;
                }
        }
       
        d->clear();
        d->len = 0;
        invalidateAll();
}


void StoryText::append(const StoryText& other)
{
        CharStyle cstyle(charStyle(length()));
        ParagraphStyle pstyle(paragraphStyle(length()));
        int cstyleStart = length();
        for (int i=0; i < other.length(); ++i) {
                if (other.charStyle(i) == cstyle)
                        continue;
                int pos = length();
                int len = i - cstyleStart;
                if (len > 0) {
                        insertChars(length(), other.text(cstyleStart, len));
                        applyCharStyle(pos, len, cstyle);
                }
                if (other.text(i) == SpecialChars::PARSEP) {
                        insertChars(pos+len, SpecialChars::PARSEP);
                        applyStyle(pos+len, other.paragraphStyle(i));
                        cstyleStart = i+1;
                }
                else {
                        cstyle = other.charStyle(i);
                        cstyleStart = i;
                }
        }
        int pos = length();
        int len = other.length() - cstyleStart;
        if (len > 0) {
                insertChars(length(), other.text(cstyleStart, len));
                applyCharStyle(pos, len, cstyle);
        }
        setDefaultStyle(other.defaultStyle());
}



/**
     A CharStyle's parent is usually the charstyle of default paragraphstyle,
     unless explicitly changed. This routines makes sure that all parent pointers
     to the default paragraphStyle's charstyle are up-to-date
 */

static void replaceParentStyle(StoryText* that, int pos, const CharStyle* oldP, const CharStyle* newP)
{
        for(int i = pos-1; i >= 0; ++i) {
                ScText* it = that->item(i);
                if (it->ch[0] == SpecialChars::PARSEP)
                        break;
                else if (it->parent() == NULL || it->parent() == oldP)
                        it->setParent(newP);
        }      
}


/**
    Make sure that the paragraph CharStyle's point to the new ParagraphStyle
 */

static void insertParSep(StoryText* that, int pos)
{
        ScText* it = that->item(pos);
        const ParagraphStyle& pstyle(that->paragraphStyle(pos));
        if(!it->parstyle)
                it->parstyle = new ParagraphStyle(pstyle);
        const CharStyle* oldP = & pstyle.charStyle();
        const CharStyle* newP = & it->parstyle->charStyle();
        replaceParentStyle(that, pos, oldP, newP);
}
/**
     need to remove the ParagraphStyle structure and replace all pointers
     to it...
 */

static void removeParSep(StoryText* that, int pos)
{
        ScText* it = that->item(pos);
        if (it->parstyle) {
                const CharStyle* oldP = & it->parstyle->charStyle();
                const CharStyle* newP = & that->paragraphStyle(pos+1).charStyle();
                replaceParentStyle(that, pos, oldP, newP);
                delete it->parstyle;
                it->parstyle = 0;
        }
}

void StoryText::removeChars(int pos, uint len)
{
        if (pos < 0)
                pos += length();

        assert( len > 0 );
        assert( pos >= 0 );
        assert( pos + static_cast<int>(len) <= length() );

        for ( int i=pos + static_cast<int>(len) - 1; i >= pos; --i )
        {
                ScText *it = d->at(i);
                if ((it->ch[0] == SpecialChars::OBJECT) && (it->cembedded != 0)) {
                        doc->FrameItems.remove(it->cembedded);
                        it->cembedded = 0;
                }
                else if ((it->ch[0] == SpecialChars::PARSEP)) {
                        removeParSep(this, i);
                }
//              qDebug("remove char %d at %d", it->ch[0].unicode(), i);
                d->take(i);
                d->len--;
                delete it;
        }

        d->len = d->count();
        invalidate(pos, -1);
}

void StoryText::insertChars(int pos, QString txt) //, const CharStyle&
{
        if (pos < 0)
                pos += length();

        assert(pos >= 0);
        assert(pos <= length());
       
        if (txt.length() == 0)
                return;
       
        const CharStyle style = pos == 0 ? defaultStyle().charStyle() : charStyle(pos - 1);
        assert( !style.font().isNone() );
       
//      const int paraStyle = pos == 0 ? 0 : at(pos - 1)->cab;
       
        for (uint i = 0; i < txt.length(); ++i) {
                ScText * item = new ScText();
                item->ch= txt.mid(i, 1);
                *static_cast<CharStyle *>(item) = style;
                d->insert(pos + i, item);
                d->len++;
                if (item->ch[0] == SpecialChars::PARSEP) {
//                      qDebug(QString("new PARSEP %2 at %1").arg(pos).arg(paragraphStyle(pos).name()));
                        insertParSep(this, pos + i);
                }
        }

        d->len = d->count();
        invalidate(pos, pos + txt.length());
}

void StoryText::replaceChar(int pos, QChar ch)
{
        if (pos < 0)
                pos += length();

        assert(pos >= 0);
        assert(pos < length());

        ScText* item = d->at(pos);
        if (item->ch[0] == ch)
                return;
       
        if (d->at(pos)->ch[0] == SpecialChars::PARSEP) {
                removeParSep(this, pos);
        }
        item->ch = ch;
        if (d->at(pos)->ch[0] == SpecialChars::PARSEP) {
                insertParSep(this, pos);
        }
       
        invalidate(pos, pos + 1);
}

void StoryText::hyphenateWord(int pos, uint len, char* hyphens)
{
        assert(pos >= 0);
        assert(pos + signed(len) <= length());
       
        for (int i=pos; i < pos+signed(len); ++i)
                if(hyphens && hyphens[i] & 1)
                        d->at(i)->setEffects(d->at(i)->effects() | ScStyle_HyphenationPossible);
                else
                        d->at(i)->setEffects(d->at(i)->effects() & ~ScStyle_HyphenationPossible);

        invalidate(pos, pos + len);
}

void StoryText::insertObject(int pos, PageItem* ob)
{
        if (pos < 0)
                pos += length();

        insertChars(pos, SpecialChars::OBJECT);
        const_cast<StoryText *>(this)->d->at(pos)->cembedded = ob;
}


int StoryText::length() const
{
        return d->len;
}

QChar StoryText::text(int pos) const
{
        if (pos < 0)
                pos += length();

        assert(pos >= 0);
        assert(pos < length());

        return const_cast<StoryText *>(this)->d->at(pos)->ch[0];
}

QString StoryText::text(int pos, uint len) const
{
        if (pos < 0)
                pos += length();

        assert(len > 0);
        assert(pos >= 0);
        assert(pos + signed(len) <= length());

        QString result;
        StoryText* that(const_cast<StoryText*>(this));
        that->d->at(pos);
        for (int i = pos; i < pos+signed(len); ++i) {
                result += that->d->current()->ch;
                that->d->next();
        }

        return result;
}

PageItem* StoryText::object(int pos) const
{
        if (pos < 0)
                pos += length();

        assert(pos >= 0);
        assert(pos < length());

        StoryText* that = const_cast<StoryText *>(this);       
        if (that->d->at(pos)->cembedded != NULL)
                return that->d->at(pos)->cembedded;
        else
                return NULL;
}


const CharStyle & StoryText::charStyle(int pos) const
{
        if (pos < 0)
                pos += length();

        assert(pos >= 0);
        assert(pos <= length());

        if (length() == 0)
                return defaultStyle().charStyle();
        else if (pos == length())
                --pos;
       
        StoryText* that = const_cast<StoryText *>(this);
        return dynamic_cast<const CharStyle &> (*that->d->at(pos));
}

const ParagraphStyle & StoryText::paragraphStyle(int pos) const
{
        if (pos < 0)
                pos += length();

        assert(pos >= 0);
        assert(pos <= length());

        StoryText * that = const_cast<StoryText *> (this);
//      assert( that->at(pos)->cab >= 0 );
//      assert( that->at(pos)->cab < doc->docParagraphStyles.count() );
//      return doc->docParagraphStyles[that->at(pos)->cab];
       
        that->d->at(pos);
        while (pos < length() && that->d->current()->ch[0] != SpecialChars::PARSEP) {
                ++pos;
                that->d->next();
        }
        if (pos == length()) {
//              qDebug(QString("using default parstyle at end (%1)").arg(pos));
                return that->d->defaultStyle;
        }
        else if ( !that->d->current()->parstyle ) {
                qDebug(QString("inserting default parstyle at %1").arg(pos));
                that->d->current()->parstyle = new ParagraphStyle();
                that->d->current()->parstyle->setParent( & that->d->defaultStyle);
                that->d->current()->parstyle->charStyle().setParent( & that->d->defaultStyle.charStyle() );
        }
        else {
//              qDebug(QString("using parstyle at %1").arg(pos));
        }
        return *that->d->current()->parstyle;
}

const ParagraphStyle& StoryText::defaultStyle() const
{
        return d->defaultStyle;
}


void StoryText::setDefaultStyle(const ParagraphStyle& style)
{
        d->defaultStyle = style;
        if (!d->defaultStyle.parent())
                d->defaultStyle.setParent( & doc->docParagraphStyles[0] );
        if (!d->defaultStyle.charStyle().parent())
                d->defaultStyle.charStyle().setParent( & doc->docParagraphStyles[0].charStyle() );
}



void StoryText::applyCharStyle(int pos, uint len, const CharStyle& style )
{
        if (pos < 0)
                pos += length();

        assert(pos >= 0);
        assert(pos + signed(len) <= length());

        if (len == 0)
                return;

        d->at(pos);
        for (uint i=pos; i < pos+len; ++i) {
                d->current()->applyCharStyle(style);
                d->next();
        }

        invalidate(pos, pos + len);
}



void StoryText::eraseCharStyle(int pos, uint len, const CharStyle& style )
{
        if (pos < 0)
                pos += length();
       
        assert(pos >= 0);
        assert(pos + signed(len) <= length());
       
        if (len == 0)
                return;
       
        d->at(pos);
        for (uint i=pos; i < pos+len; ++i) {
                d->current()->eraseCharStyle(style);
                d->next();
        }
       
        invalidate(pos, pos + len);
}


void StoryText::applyStyle(int pos, const ParagraphStyle& style)
{
        if (pos < 0)
                pos += length();

        assert(pos >= 0);
        assert(pos <= length());

/*      int paraStyle = QMAX(0, findParagraphStyle(doc, style)); //FIXME:NLS
        int i = pos - 1;
        if (length() > 0) {
                // find start of para
                while (i >= 0 && at(i)->ch[0] != SpecialChars::PARSEP) {
                        --i;
                }
                // set 'cab' field
                do {
                        ++i;
                        at(i)->cab = paraStyle;
                }
                while (i+1 < length() && d->at(i)->ch[0] != SpecialChars::PARSEP);
        }
        ++i; */

       
        int i = pos;
        while (i < length() && d->at(i)->ch[0] != SpecialChars::PARSEP) {
                ++i;
        }
        if (i < length()) {
                if (!d->at(i)->parstyle) {
                        qDebug(QString("PARSEP without style at pos %1").arg(i));
                        d->at(i)->parstyle = new ParagraphStyle();
                }
//              qDebug(QString("applying parstyle %2 at %1 for %3").arg(i).arg(paragraphStyle(pos).name()).arg(pos));
                d->at(i)->parstyle->applyStyle(style);
                if (!d->at(i)->parstyle->parent()) {
                        d->at(i)->parstyle->setParent( & d->defaultStyle );
                }
                if (!d->at(i)->parstyle->charStyle().parent()) {
                        d->at(i)->parstyle->charStyle().setParent( & d->defaultStyle.charStyle() );
                }
        }
        else {
                // not happy about this but inserting a new PARSEP makes more trouble
//              qDebug(QString("applying parstyle %1 as defaultstyle for %2").arg(paragraphStyle(pos).name()).arg(pos));
                d->defaultStyle.applyStyle(style);
        }
}

void StoryText::eraseStyle(int pos, const ParagraphStyle& style)
{
        if (pos < 0)
                pos += length();
       
        assert(pos >= 0);
        assert(pos <= length());
               
        int i = pos;
        while (i < length() && d->at(i)->ch[0] != SpecialChars::PARSEP) {
                ++i;
        }
        if (i < length()) {
                if (!d->at(i)->parstyle) {
                        qDebug(QString("PARSEP without style at pos %1").arg(i));
                        d->at(i)->parstyle = new ParagraphStyle();
                }
                //              qDebug(QString("applying parstyle %2 at %1 for %3").arg(i).arg(paragraphStyle(pos).name()).arg(pos));
                d->at(i)->parstyle->eraseStyle(style);
                if (!d->at(i)->parstyle->parent()) {
                        d->at(i)->parstyle->setParent( & d->defaultStyle );
                }
                if (!d->at(i)->parstyle->charStyle().parent()) {
                        d->at(i)->parstyle->charStyle().setParent( & d->defaultStyle.charStyle() );
                }
        }
        else {
                // not happy about this but inserting a new PARSEP makes more trouble
                //              qDebug(QString("applying parstyle %1 as defaultstyle for %2").arg(paragraphStyle(pos).name()).arg(pos));
                d->defaultStyle.eraseStyle(style);
        }
}


uint StoryText::nrOfParagraphs() const
{
        uint result = 0;
        StoryText* that = const_cast<StoryText *>(this);
        that->d->at(0);
        for (int i=0; i < length(); ++i) {
                if (that->d->current()->ch[0] == SpecialChars::PARSEP)
                        ++result;
                that->d->next();
        }
        return result;
}

int StoryText::startOfParagraph(uint index) const
{
        if (index == 0)
                return 0;

        StoryText* that = const_cast<StoryText *>(this);
        that->d->at(0);
        for (int i=0; i < length(); ++i) {
                if (that->d->current()->ch[0] == SpecialChars::PARSEP && ! --index)
                        return i + 1;
                that->d->next();
        }
        return -1;
}

int StoryText::endOfParagraph(uint index) const
{
        ++index;
        StoryText* that = const_cast<StoryText *>(this);
        that->d->at(0);
        for (int i=0; i < length(); ++i) {
                if (that->d->current()->ch[0] == SpecialChars::PARSEP && ! --index)
                        return i;
                that->d->next();
        }
        return length();
}

uint StoryText::nrOfRuns() const
{
        return length();
}

int StoryText::startOfRun(uint index) const
{
        return index;
}

int StoryText::endOfRun(uint index) const
{
        return index + 1;
}


// selection

int StoryText::startOfSelection() const
{
        return selFirst <= selLast? selFirst : 0;
}

int StoryText::endOfSelection() const
{
        return selFirst <= selLast? selLast + 1 : -1;
}

int StoryText::lengthOfSelection() const
{
        return selFirst <= selLast? selLast - selFirst + 1 : 0;
}


bool StoryText::selected(int pos) const
{
        return (selFirst <= pos && pos <= selLast)
//             || (pos >= 0 && pos < length() && const_cast<StoryText*>(this)->d->at(pos)->cselect)
        ;
}

void StoryText::select(int pos, uint len, bool on)
{
        if (pos < 0)
                pos += length();

        assert( pos >= 0 );
        assert( pos + signed(len) <= length() );

//      qDebug("old selection: %d - %d", selFirst, selLast);

//      StoryText* that = const_cast<StoryText *>(this);
//      for (int i=pos; i < pos+signed(len); ++i)
//              that->at(i)->cselect = on;

        if (on) {
                // extend if possible
                if (selected(pos - 1))
                        selLast = QMAX(selLast, pos + static_cast<int>(len) - 1);
                else if (selected(pos + len))
                        selFirst = QMIN(selFirst, pos);
                else {
                        selFirst = pos;
                        selLast = pos + len - 1;
                }
        }
        else {
                if (pos <= selFirst && selLast < pos + signed(len))
                        deselectAll();
                // shrink
                else if (!selected(pos - 1) && selected(pos + len - 1))
                        selFirst = pos + len;
                else if (selected(pos) && !selected(pos + len))
                        selLast = pos - 1;
                else if (selected(pos) || selected(pos + len - 1))
                        // Grr, deselection splits selection
                        selLast = pos - 1;
        }
       
//      qDebug("new selection: %d - %d", selFirst, selLast);
}

void StoryText::selectAll()
{
/*      StoryText* that = const_cast<StoryText *>(this);
        that->at(0);
        for (int i=0; i < length(); ++i) {
                that->current()->cselect = true;
                that->next();
        }
*/

        selFirst = 0;
        selLast = length() - 1;
}

void StoryText::deselectAll()
{
/*      StoryText* that = const_cast<StoryText *>(this);
        that->at(0);
        for (int i=0; i < length(); ++i) {
                that->current()->cselect = false;
                that->next();
        }
*/
     
        selFirst = 0;
        selLast = -1;
}

void StoryText::removeSelection()
{
//      qDebug("removeSelection: %d - %d", selFirst, selLast);
        if (selFirst > selLast)
                return;

        assert( selFirst >= 0 );
        assert( selLast < length() );

        removeChars(selFirst, selLast - selFirst+1);
        deselectAll();
}



void StoryText::invalidateObject(const PageItem * embedded)
{
}

void StoryText::invalidateLayout()
{
}

void StoryText::invalidateAll()
{
}

void StoryText::invalidate(int firstitem, int lastitem)
{
}


// physical view

/*
void StoryText::validate()
{
        static bool withinValidate = false;
       
        assert( !withinValidate );
        withinValidate = true;
       
        withinValidate = false;
}
*/


int StoryText::screenToPosition(FPoint coord) const
{
//FIXME:NLS    
        //FIXME: should be end of lastFrameItem
        return length();
}


FPoint StoryText::boundingBox(int pos, uint len) const
{
        return FPoint(); //FIXME
}


int StoryText::layout(int startItem)
{
        //FIXME:NLS
        return -1;
}


uint StoryText::nrOfItems() const
{
        return length();
}


ScText*  StoryText::item(uint itm)
{
        assert( static_cast<int>(itm) < length() );
        return const_cast<StoryText *>(this)->d->at(itm);
}

const QString StoryText::itemText(uint itm) const
{
       
        assert( static_cast<int>(itm) < length() );

        return text(itm, 1);
}


const CharStyle StoryText::itemStyle(uint itm) const
{
        assert( static_cast<int>(itm) < length() );

        return charStyle(itm);
}
       

int StoryText::startOfItem(uint itm) const
{
        assert( static_cast<int>(itm) < length() );

        return itm;
}

int StoryText::endOfItem(uint itm) const
{
        assert( static_cast<int>(itm) < length() );

        return itm + 1;
}