src/qt3support/text/q3richtext.cpp

Go to the documentation of this file.
00001 /****************************************************************************
00002 **
00003 ** Copyright (C) 1992-2006 Trolltech ASA. All rights reserved.
00004 **
00005 ** This file is part of the Qt3Support module of the Qt Toolkit.
00006 **
00007 ** This file may be used under the terms of the GNU General Public
00008 ** License version 2.0 as published by the Free Software Foundation
00009 ** and appearing in the file LICENSE.GPL included in the packaging of
00010 ** this file.  Please review the following information to ensure GNU
00011 ** General Public Licensing requirements will be met:
00012 ** http://www.trolltech.com/products/qt/opensource.html
00013 **
00014 ** If you are unsure which license is appropriate for your use, please
00015 ** review the following information:
00016 ** http://www.trolltech.com/products/qt/licensing.html or contact the
00017 ** sales department at sales@trolltech.com.
00018 **
00019 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
00020 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
00021 **
00022 ****************************************************************************/
00023 
00024 #include "q3richtext_p.h"
00025 
00026 #ifndef QT_NO_RICHTEXT
00027 
00028 #include "qbitmap.h"
00029 #include "qapplication.h"
00030 #include "q3cleanuphandler.h"
00031 #include "qcursor.h"
00032 #include "qdatastream.h"
00033 #include "q3dragobject.h"
00034 #include "qdrawutil.h"
00035 #include "qfile.h"
00036 #include "qfileinfo.h"
00037 #include "qfont.h"
00038 #include "qimage.h"
00039 #include "qmap.h"
00040 #include "qmime.h"
00041 #include "q3paintdevicemetrics.h"
00042 #include "qpainter.h"
00043 #include "qstringlist.h"
00044 #include "qstyle.h"
00045 #include "qstyleoption.h"
00046 #include "q3stylesheet.h"
00047 #include "qtextstream.h"
00048 #include <private/qtextengine_p.h>
00049 #include <private/qunicodetables_p.h>
00050 
00051 #include <stdlib.h>
00052 
00053 #if defined(Q_WS_X11)
00054 #include "qx11info_x11.h"
00055 #endif
00056 
00057 static Q3TextCursor* richTextExportStart = 0;
00058 static Q3TextCursor* richTextExportEnd = 0;
00059 
00060 class Q3TextFormatCollection;
00061 
00062 const int border_tolerance = 2;
00063 
00064 #ifdef Q_WS_WIN
00065 #include "qt_windows.h"
00066 #endif
00067 
00068 static inline bool is_printer(QPainter *p)
00069 {
00070     if (!p || !p->device())
00071         return false;
00072     return p->device()->devType() == QInternal::Printer;
00073 }
00074 
00075 static inline int scale(int value, QPainter *painter)
00076 {
00077     if (is_printer(painter)) {
00078         Q3PaintDeviceMetrics metrics(painter->device());
00079 #if defined(Q_WS_X11)
00080         value = value * metrics.logicalDpiY() /
00081                 QX11Info::appDpiY(painter->device()->x11Screen());
00082 #elif defined (Q_WS_WIN)
00083         HDC hdc = GetDC(0);
00084         int gdc = GetDeviceCaps(hdc, LOGPIXELSY);
00085         if (gdc)
00086             value = value * metrics.logicalDpiY() / gdc;
00087         ReleaseDC(0, hdc);
00088 #elif defined (Q_WS_MAC)
00089         value = value * metrics.logicalDpiY() / 75; // ##### FIXME
00090 #elif defined (Q_WS_QWS)
00091         value = value * metrics.logicalDpiY() / 75;
00092 #endif
00093     }
00094     return value;
00095 }
00096 
00097 
00098 static inline bool isBreakable(Q3TextString *string, int pos)
00099 {
00100     if (string->at(pos).nobreak)
00101         return false;
00102     return (pos < string->length()-1 && string->at(pos+1).softBreak);
00103 }
00104 
00105 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
00106 
00107 void Q3TextCommandHistory::addCommand(Q3TextCommand *cmd)
00108 {
00109     if (current < history.count() - 1) {
00110         QList<Q3TextCommand *> commands;
00111 
00112         for (int i = 0; i <= current; ++i)
00113             commands.insert(i, history.takeFirst());
00114 
00115         commands.append(cmd);
00116         while (!history.isEmpty())
00117             delete history.takeFirst();
00118         history = commands;
00119     } else {
00120         history.append(cmd);
00121     }
00122 
00123     if (history.count() > steps)
00124         delete history.takeFirst();
00125     else
00126         ++current;
00127 }
00128 
00129 Q3TextCursor *Q3TextCommandHistory::undo(Q3TextCursor *c)
00130 {
00131     if (current > -1) {
00132         Q3TextCursor *c2 = history.at(current)->unexecute(c);
00133         --current;
00134         return c2;
00135     }
00136     return 0;
00137 }
00138 
00139 Q3TextCursor *Q3TextCommandHistory::redo(Q3TextCursor *c)
00140 {
00141     if (current > -1) {
00142         if (current < history.count() - 1) {
00143             ++current;
00144             return history.at(current)->execute(c);
00145         }
00146     } else {
00147         if (history.count() > 0) {
00148             ++current;
00149             return history.at(current)->execute(c);
00150         }
00151     }
00152     return 0;
00153 }
00154 
00155 bool Q3TextCommandHistory::isUndoAvailable()
00156 {
00157     return current > -1;
00158 }
00159 
00160 bool Q3TextCommandHistory::isRedoAvailable()
00161 {
00162    return current > -1 && current < history.count() - 1 || current == -1 && history.count() > 0;
00163 }
00164 
00165 // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
00166 
00167 Q3TextDeleteCommand::Q3TextDeleteCommand(Q3TextDocument *dc, int i, int idx, const QVector<Q3TextStringChar> &str,
00168                                         const QByteArray& oldStyleInfo)
00169     : Q3TextCommand(dc), id(i), index(idx), parag(0), text(str), styleInformation(oldStyleInfo)
00170 {
00171     for (int j = 0; j < (int)text.size(); ++j) {
00172         if (text[j].format())
00173             text[j].format()->addRef();
00174     }
00175 }
00176 
00177 Q3TextDeleteCommand::Q3TextDeleteCommand(Q3TextParagraph *p, int idx, const QVector<Q3TextStringChar> &str)
00178     : Q3TextCommand(0), id(-1), index(idx), parag(p), text(str)
00179 {
00180     for (int i = 0; i < (int)text.size(); ++i) {
00181         if (text[i].format())
00182             text[i].format()->addRef();
00183     }
00184 }
00185 
00186 Q3TextDeleteCommand::~Q3TextDeleteCommand()
00187 {
00188     for (int i = 0; i < (int)text.size(); ++i) {
00189         if (text[i].format())
00190             text[i].format()->removeRef();
00191     }
00192     text.resize(0);
00193 }
00194 
00195 Q3TextCursor *Q3TextDeleteCommand::execute(Q3TextCursor *c)
00196 {
00197     Q3TextParagraph *s = doc ? doc->paragAt(id) : parag;
00198     if (!s) {
00199         qWarning("can't locate parag at %d, last parag: %d", id, doc->lastParagraph()->paragId());
00200         return 0;
00201     }
00202 
00203     cursor.setParagraph(s);
00204     cursor.setIndex(index);
00205     int len = text.size();
00206     if (c)
00207         *c = cursor;
00208     if (doc) {
00209         doc->setSelectionStart(Q3TextDocument::Temp, cursor);
00210         for (int i = 0; i < len; ++i)
00211             cursor.gotoNextLetter();
00212         doc->setSelectionEnd(Q3TextDocument::Temp, cursor);
00213         doc->removeSelectedText(Q3TextDocument::Temp, &cursor);
00214         if (c)
00215             *c = cursor;
00216     } else {
00217         s->remove(index, len);
00218     }
00219 
00220     return c;
00221 }
00222 
00223 Q3TextCursor *Q3TextDeleteCommand::unexecute(Q3TextCursor *c)
00224 {
00225     Q3TextParagraph *s = doc ? doc->paragAt(id) : parag;
00226     if (!s) {
00227         qWarning("can't locate parag at %d, last parag: %d", id, doc->lastParagraph()->paragId());
00228         return 0;
00229     }
00230 
00231     cursor.setParagraph(s);
00232     cursor.setIndex(index);
00233     QString str = Q3TextString::toString(text);
00234     cursor.insert(str, true, &text);
00235     if (c)
00236         *c = cursor;
00237     cursor.setParagraph(s);
00238     cursor.setIndex(index);
00239 
00240 #ifndef QT_NO_DATASTREAM
00241     if (!styleInformation.isEmpty()) {
00242         QDataStream styleStream(&styleInformation, IO_ReadOnly);
00243         int num;
00244         styleStream >> num;
00245         Q3TextParagraph *p = s;
00246         while (num-- && p) {
00247             p->readStyleInformation(styleStream);
00248             p = p->next();
00249         }
00250     }
00251 #endif
00252     s = cursor.paragraph();
00253     while (s) {
00254         s->format();
00255         s->setChanged(true);
00256         if (s == c->paragraph())
00257             break;
00258         s = s->next();
00259     }
00260 
00261     return &cursor;
00262 }
00263 
00264 Q3TextFormatCommand::Q3TextFormatCommand(Q3TextDocument *dc, int sid, int sidx, int eid, int eidx,
00265                                         const QVector<Q3TextStringChar> &old, Q3TextFormat *f, int fl)
00266     : Q3TextCommand(dc), startId(sid), startIndex(sidx), endId(eid), endIndex(eidx), format(f), oldFormats(old), flags(fl)
00267 {
00268     format = dc->formatCollection()->format(f);
00269     for (int j = 0; j < (int)oldFormats.size(); ++j) {
00270         if (oldFormats[j].format())
00271             oldFormats[j].format()->addRef();
00272     }
00273 }
00274 
00275 Q3TextFormatCommand::~Q3TextFormatCommand()
00276 {
00277     format->removeRef();
00278     for (int j = 0; j < (int)oldFormats.size(); ++j) {
00279         if (oldFormats[j].format())
00280             oldFormats[j].format()->removeRef();
00281     }
00282 }
00283 
00284 Q3TextCursor *Q3TextFormatCommand::execute(Q3TextCursor *c)
00285 {
00286     Q3TextParagraph *sp = doc->paragAt(startId);
00287     Q3TextParagraph *ep = doc->paragAt(endId);
00288     if (!sp || !ep)
00289         return c;
00290 
00291     Q3TextCursor start(doc);
00292     start.setParagraph(sp);
00293     start.setIndex(startIndex);
00294     Q3TextCursor end(doc);
00295     end.setParagraph(ep);
00296     end.setIndex(endIndex);
00297 
00298     doc->setSelectionStart(Q3TextDocument::Temp, start);
00299     doc->setSelectionEnd(Q3TextDocument::Temp, end);
00300     doc->setFormat(Q3TextDocument::Temp, format, flags);
00301     doc->removeSelection(Q3TextDocument::Temp);
00302     if (endIndex == ep->length())
00303         end.gotoLeft();
00304     *c = end;
00305     return c;
00306 }
00307 
00308 Q3TextCursor *Q3TextFormatCommand::unexecute(Q3TextCursor *c)
00309 {
00310     Q3TextParagraph *sp = doc->paragAt(startId);
00311     Q3TextParagraph *ep = doc->paragAt(endId);
00312     if (!sp || !ep)
00313         return 0;
00314 
00315     int idx = startIndex;
00316     int fIndex = 0;
00317     while ( fIndex < int(oldFormats.size()) ) {
00318         if (oldFormats.at(fIndex).c == '\n') {
00319             if (idx > 0) {
00320                 if (idx < sp->length() && fIndex > 0)
00321                     sp->setFormat(idx, 1, oldFormats.at(fIndex - 1).format());
00322                 if (sp == ep)
00323                     break;
00324                 sp = sp->next();
00325                 idx = 0;
00326             }
00327             fIndex++;
00328         }
00329         if (oldFormats.at(fIndex).format())
00330             sp->setFormat(idx, 1, oldFormats.at(fIndex).format());
00331         idx++;
00332         fIndex++;
00333         if (fIndex >= (int)oldFormats.size())
00334             break;
00335         if (idx >= sp->length()) {
00336             if (sp == ep)
00337                 break;
00338             sp = sp->next();
00339             idx = 0;
00340         }
00341     }
00342 
00343     Q3TextCursor end(doc);
00344     end.setParagraph(ep);
00345     end.setIndex(endIndex);
00346     if (endIndex == ep->length())
00347         end.gotoLeft();
00348     *c = end;
00349     return c;
00350 }
00351 
00352 Q3TextStyleCommand::Q3TextStyleCommand(Q3TextDocument *dc, int fParag, int lParag, const QByteArray& beforeChange)
00353     : Q3TextCommand(dc), firstParag(fParag), lastParag(lParag), before(beforeChange)
00354 {
00355     after = readStyleInformation( dc, fParag, lParag);
00356 }
00357 
00358 
00359 QByteArray Q3TextStyleCommand::readStyleInformation( Q3TextDocument* doc, int fParag, int lParag)
00360 {
00361     QByteArray style;
00362 #ifndef QT_NO_DATASTREAM
00363     Q3TextParagraph *p = doc->paragAt(fParag);
00364     if (!p)
00365         return style;
00366     QDataStream styleStream(&style, IO_WriteOnly);
00367     int num = lParag - fParag + 1;
00368     styleStream << num;
00369     while (num -- && p) {
00370         p->writeStyleInformation(styleStream);
00371         p = p->next();
00372     }
00373 #endif
00374     return style;
00375 }
00376 
00377 void Q3TextStyleCommand::writeStyleInformation( Q3TextDocument* doc, int fParag, const QByteArray& style)
00378 {
00379 #ifndef QT_NO_DATASTREAM
00380     Q3TextParagraph *p = doc->paragAt(fParag);
00381     if (!p)
00382         return;
00383     QByteArray copy = style;
00384     QDataStream styleStream(&copy, IO_ReadOnly);
00385     int num;
00386     styleStream >> num;
00387     while (num-- && p) {
00388         p->readStyleInformation(styleStream);
00389         p = p->next();
00390     }
00391 #endif
00392 }
00393 
00394 Q3TextCursor *Q3TextStyleCommand::execute(Q3TextCursor *c)
00395 {
00396     writeStyleInformation(doc, firstParag, after);
00397     return c;
00398 }
00399 
00400 Q3TextCursor *Q3TextStyleCommand::unexecute(Q3TextCursor *c)
00401 {
00402     writeStyleInformation(doc, firstParag, before);
00403     return c;
00404 }
00405 
00406 // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
00407 
00408 Q3TextCursor::Q3TextCursor(Q3TextDocument *dc)
00409     : idx(0), tmpX(-1), ox(0), oy(0),
00410       valid(true)
00411 {
00412     para = dc ? dc->firstParagraph() : 0;
00413 }
00414 
00415 Q3TextCursor::Q3TextCursor(const Q3TextCursor &c)
00416 {
00417     ox = c.ox;
00418     oy = c.oy;
00419     idx = c.idx;
00420     para = c.para;
00421     tmpX = c.tmpX;
00422     indices = c.indices;
00423     paras = c.paras;
00424     xOffsets = c.xOffsets;
00425     yOffsets = c.yOffsets;
00426     valid = c.valid;
00427 }
00428 
00429 Q3TextCursor::~Q3TextCursor()
00430 {
00431 }
00432 
00433 Q3TextCursor &Q3TextCursor::operator=(const Q3TextCursor &c)
00434 {
00435     ox = c.ox;
00436     oy = c.oy;
00437     idx = c.idx;
00438     para = c.para;
00439     tmpX = c.tmpX;
00440     indices = c.indices;
00441     paras = c.paras;
00442     xOffsets = c.xOffsets;
00443     yOffsets = c.yOffsets;
00444     valid = c.valid;
00445 
00446     return *this;
00447 }
00448 
00449 bool Q3TextCursor::operator==(const Q3TextCursor &c) const
00450 {
00451     return para == c.para && idx == c.idx;
00452 }
00453 
00454 int Q3TextCursor::totalOffsetX() const
00455 {
00456     int xoff = ox;
00457     for (QStack<int>::ConstIterator xit = xOffsets.begin(); xit != xOffsets.end(); ++xit)
00458         xoff += *xit;
00459     return xoff;
00460 }
00461 
00462 int Q3TextCursor::totalOffsetY() const
00463 {
00464     int yoff = oy;
00465     for (QStack<int>::ConstIterator yit = yOffsets.begin(); yit != yOffsets.end(); ++yit)
00466         yoff += *yit;
00467     return yoff;
00468 }
00469 
00470 #ifndef QT_NO_TEXTCUSTOMITEM
00471 void Q3TextCursor::gotoIntoNested(const QPoint &globalPos)
00472 {
00473     if (!para)
00474         return;
00475     Q_ASSERT(para->at(idx)->isCustom());
00476     push();
00477     ox = 0;
00478     int bl, y;
00479     para->lineHeightOfChar(idx, &bl, &y);
00480     oy = y + para->rect().y();
00481     ox = para->at(idx)->x;
00482     Q3TextDocument* doc = document();
00483     para->at(idx)->customItem()->enterAt(this, doc, para, idx, ox, oy, globalPos-QPoint(ox,oy));
00484 }
00485 #endif
00486 
00487 void Q3TextCursor::invalidateNested()
00488 {
00489     if (nestedDepth()) {
00490         QStack<Q3TextParagraph*>::Iterator it = paras.begin();
00491         QStack<int>::Iterator it2 = indices.begin();
00492         for (; it != paras.end(); ++it, ++it2) {
00493             if (*it == para)
00494                 continue;
00495             (*it)->invalidate(0);
00496 #ifndef QT_NO_TEXTCUSTOMITEM
00497             if ((*it)->at(*it2)->isCustom())
00498                 (*it)->at(*it2)->customItem()->invalidate();
00499 #endif
00500         }
00501     }
00502 }
00503 
00504 void Q3TextCursor::insert(const QString &str, bool checkNewLine, QVector<Q3TextStringChar> *formatting)
00505 {
00506     tmpX = -1;
00507     bool justInsert = true;
00508     QString s(str);
00509 #if defined(Q_WS_WIN)
00510     if (checkNewLine) {
00511         int i = 0;
00512         while ((i = s.indexOf('\r', i)) != -1)
00513             s.remove(i ,1);
00514     }
00515 #endif
00516     if (checkNewLine)
00517         justInsert = s.indexOf('\n') == -1;
00518     if (justInsert) { // we ignore new lines and insert all in the current para at the current index
00519         para->insert(idx, s.unicode(), s.length());
00520         if (formatting) {
00521             for (int i = 0; i < (int)s.length(); ++i) {
00522                 if (formatting->at(i).format()) {
00523                     formatting->at(i).format()->addRef();
00524                     para->string()->setFormat(idx + i, formatting->at(i).format(), true);
00525                 }
00526             }
00527         }
00528         idx += s.length();
00529     } else { // we split at new lines
00530         int start = -1;
00531         int end;
00532         int y = para->rect().y() + para->rect().height();
00533         int lastIndex = 0;
00534         do {
00535             end = s.indexOf('\n', start + 1); // find line break
00536             if (end == -1) // didn't find one, so end of line is end of string
00537                 end = s.length();
00538             int len = (start == -1 ? end : end - start - 1);
00539             if (len > 0) // insert the line
00540                 para->insert(idx, s.unicode() + start + 1, len);
00541             else
00542                 para->invalidate(0);
00543             if (formatting) { // set formats to the chars of the line
00544                 for (int i = 0; i < len; ++i) {
00545                     if (formatting->at(i + lastIndex).format()) {
00546                         formatting->at(i + lastIndex).format()->addRef();
00547                         para->string()->setFormat(i + idx, formatting->at(i + lastIndex).format(), true);
00548                     }
00549                 }
00550                 lastIndex += len;
00551             }
00552             start = end; // next start is at the end of this line
00553             idx += len; // increase the index of the cursor to the end of the inserted text
00554             if (s[end] == '\n') { // if at the end was a line break, break the line
00555                 splitAndInsertEmptyParagraph(false, true);
00556                 para->setEndState(-1);
00557                 para->prev()->format(-1, false);
00558                 lastIndex++;
00559             }
00560 
00561         } while (end < (int)s.length());
00562 
00563         para->format(-1, false);
00564         int dy = para->rect().y() + para->rect().height() - y;
00565         Q3TextParagraph *p = para;
00566         p->setParagId(p->prev() ? p->prev()->paragId() + 1 : 0);
00567         p = p->next();
00568         while (p) {
00569             p->setParagId(p->prev()->paragId() + 1);
00570             p->move(dy);
00571             p->invalidate(0);
00572             p->setEndState(-1);
00573             p = p->next();
00574         }
00575     }
00576 
00577     int h = para->rect().height();
00578     para->format(-1, true);
00579     if (h != para->rect().height())
00580         invalidateNested();
00581     else if (para->document() && para->document()->parent())
00582         para->document()->nextDoubleBuffered = true;
00583 
00584     fixCursorPosition();
00585 }
00586 
00587 void Q3TextCursor::gotoLeft()
00588 {
00589     if (para->string()->isRightToLeft())
00590         gotoNextLetter();
00591     else
00592         gotoPreviousLetter();
00593 }
00594 
00595 void Q3TextCursor::gotoPreviousLetter()
00596 {
00597     tmpX = -1;
00598 
00599     if (idx > 0) {
00600         idx = para->string()->previousCursorPosition(idx);
00601 #ifndef QT_NO_TEXTCUSTOMITEM
00602         const Q3TextStringChar *tsc = para->at(idx);
00603         if (tsc && tsc->isCustom() && tsc->customItem()->isNested())
00604             processNesting(EnterEnd);
00605 #endif
00606     } else if (para->prev()) {
00607         para = para->prev();
00608         while (!para->isVisible() && para->prev())
00609             para = para->prev();
00610         idx = para->length() - 1;
00611     } else if (nestedDepth()) {
00612         pop();
00613         processNesting(Prev);
00614         if (idx == -1) {
00615             pop();
00616             if (idx > 0) {
00617                 idx = para->string()->previousCursorPosition(idx);
00618 #ifndef QT_NO_TEXTCUSTOMITEM
00619                 const Q3TextStringChar *tsc = para->at(idx);
00620                 if (tsc && tsc->isCustom() && tsc->customItem()->isNested())
00621                     processNesting(EnterEnd);
00622 #endif
00623             } else if (para->prev()) {
00624                 para = para->prev();
00625                 idx = para->length() - 1;
00626             }
00627         }
00628     }
00629 }
00630 
00631 void Q3TextCursor::push()
00632 {
00633     indices.push(idx);
00634     paras.push(para);
00635     xOffsets.push(ox);
00636     yOffsets.push(oy);
00637 }
00638 
00639 void Q3TextCursor::pop()
00640 {
00641     if (indices.isEmpty())
00642         return;
00643     idx = indices.pop();
00644     para = paras.pop();
00645     ox = xOffsets.pop();
00646     oy = yOffsets.pop();
00647 }
00648 
00649 void Q3TextCursor::restoreState()
00650 {
00651     while (!indices.isEmpty())
00652         pop();
00653 }
00654 
00655 bool Q3TextCursor::place(const QPoint &p, Q3TextParagraph *s, bool link)
00656 {
00657     QPoint pos(p);
00658     QRect r;
00659     Q3TextParagraph *str = s;
00660     if (pos.y() < s->rect().y()) {
00661         pos.setY(s->rect().y());
00662 #ifdef Q_WS_MAC
00663         pos.setX(s->rect().x());
00664 #endif
00665     }
00666     while (s) {
00667         r = s->rect();
00668         r.setWidth(document() ? document()->width() : QWIDGETSIZE_MAX);
00669         if (s->isVisible())
00670             str = s;
00671         if (pos.y() >= r.y() && pos.y() <= r.y() + r.height())
00672             break;
00673         if (!s->next()) {
00674 #ifdef Q_WS_MAC
00675             pos.setX(s->rect().x() + s->rect().width());
00676 #endif
00677             break;
00678         }
00679         s = s->next();
00680     }
00681 
00682     if (!s || !str)
00683         return false;
00684 
00685     s = str;
00686 
00687     setParagraph(s);
00688     int y = s->rect().y();
00689     int lines = s->lines();
00690     Q3TextStringChar *chr = 0;
00691     int index = 0;
00692     int i = 0;
00693     int cy = 0;
00694     int ch = 0;
00695     for (; i < lines; ++i) {
00696         chr = s->lineStartOfLine(i, &index);
00697         cy = s->lineY(i);
00698         ch = s->lineHeight(i);
00699         if (!chr)
00700             return false;
00701         if (pos.y() <= y + cy + ch)
00702             break;
00703     }
00704     int nextLine;
00705     if (i < lines - 1)
00706         s->lineStartOfLine(i+1, &nextLine);
00707     else
00708         nextLine = s->length();
00709     i = index;
00710     int x = s->rect().x();
00711     if (pos.x() < x)
00712         pos.setX(x + 1);
00713     int cw;
00714     int curpos = s->length()-1;
00715     int dist = 10000000;
00716     bool inCustom = false;
00717     while (i < nextLine) {
00718         chr = s->at(i);
00719         int cpos = x + chr->x;
00720         cw = s->string()->width(i);
00721 #ifndef QT_NO_TEXTCUSTOMITEM
00722         if (chr->isCustom() && chr->customItem()->isNested()) {
00723             if (pos.x() >= cpos && pos.x() <= cpos + cw &&
00724                  pos.y() >= y + cy && pos.y() <= y + cy + chr->height()) {
00725                 inCustom = true;
00726                 curpos = i;
00727                 break;
00728             }
00729         } else
00730 #endif
00731         {
00732             if(chr->rightToLeft)
00733                 cpos += cw;
00734             int diff = cpos - pos.x();
00735             bool dm = diff < 0 ? !chr->rightToLeft : chr->rightToLeft;
00736             if ((QABS(diff) < dist || (dist == diff && dm == true)) && para->string()->validCursorPosition(i)) {
00737                 dist = QABS(diff);
00738                 if (!link || pos.x() >= x + chr->x)
00739                     curpos = i;
00740             }
00741         }
00742         i++;
00743     }
00744     setIndex(curpos);
00745 
00746 #ifndef QT_NO_TEXTCUSTOMITEM
00747     if (inCustom && para->document() && para->at(curpos)->isCustom() && para->at(curpos)->customItem()->isNested()) {
00748         Q3TextDocument *oldDoc = para->document();
00749         gotoIntoNested(pos);
00750         if (oldDoc == para->document())
00751             return true;
00752         QPoint p(pos.x() - offsetX(), pos.y() - offsetY());
00753         if (!place(p, document()->firstParagraph(), link))
00754             pop();
00755     }
00756 #endif
00757     return true;
00758 }
00759 
00760 bool Q3TextCursor::processNesting(Operation op)
00761 {
00762     if (!para->document())
00763         return false;
00764     Q3TextDocument* doc = para->document();
00765     push();
00766     ox = para->at(idx)->x;
00767     int bl, y;
00768     para->lineHeightOfChar(idx, &bl, &y);
00769     oy = y + para->rect().y();
00770     bool ok = false;
00771 
00772 #ifndef QT_NO_TEXTCUSTOMITEM
00773     switch (op) {
00774     case EnterBegin:
00775         ok = para->at(idx)->customItem()->enter(this, doc, para, idx, ox, oy);
00776         break;
00777     case EnterEnd:
00778         ok = para->at(idx)->customItem()->enter(this, doc, para, idx, ox, oy, true);
00779         break;
00780     case Next:
00781         ok = para->at(idx)->customItem()->next(this, doc, para, idx, ox, oy);
00782         break;
00783     case Prev:
00784         ok = para->at(idx)->customItem()->prev(this, doc, para, idx, ox, oy);
00785         break;
00786     case Down:
00787         ok = para->at(idx)->customItem()->down(this, doc, para, idx, ox, oy);
00788         break;
00789     case Up:
00790         ok = para->at(idx)->customItem()->up(this, doc, para, idx, ox, oy);
00791         break;
00792     }
00793     if (!ok)
00794 #endif
00795         pop();
00796     return ok;
00797 }
00798 
00799 void Q3TextCursor::gotoRight()
00800 {
00801     if (para->string()->isRightToLeft())
00802         gotoPreviousLetter();
00803     else
00804         gotoNextLetter();
00805 }
00806 
00807 void Q3TextCursor::gotoNextLetter()
00808 {
00809    tmpX = -1;
00810 
00811 #ifndef QT_NO_TEXTCUSTOMITEM
00812     const Q3TextStringChar *tsc = para->at(idx);
00813     if (tsc && tsc->isCustom() && tsc->customItem()->isNested()) {
00814         if (processNesting(EnterBegin))
00815             return;
00816     }
00817 #endif
00818 
00819     if (idx < para->length() - 1) {
00820         idx = para->string()->nextCursorPosition(idx);
00821     } else if (para->next()) {
00822         para = para->next();
00823         while (!para->isVisible() && para->next())
00824             para = para->next();
00825         idx = 0;
00826     } else if (nestedDepth()) {
00827         pop();
00828         processNesting(Next);
00829         if (idx == -1) {
00830             pop();
00831             if (idx < para->length() - 1) {
00832                 idx = para->string()->nextCursorPosition(idx);
00833             } else if (para->next()) {
00834                 para = para->next();
00835                 idx = 0;
00836             }
00837         }
00838     }
00839 }
00840 
00841 void Q3TextCursor::gotoUp()
00842 {
00843     int indexOfLineStart;
00844     int line;
00845     Q3TextStringChar *c = para->lineStartOfChar(idx, &indexOfLineStart, &line);
00846     if (!c)
00847         return;
00848 
00849     if (tmpX < 0)
00850         tmpX = x();
00851 
00852     if (indexOfLineStart == 0) {
00853         if (!para->prev()) {
00854             if (!nestedDepth())
00855                 return;
00856             pop();
00857             processNesting(Up);
00858             if (idx == -1) {
00859                 pop();
00860                 if (!para->prev())
00861                     return;
00862                 idx = tmpX = 0;
00863             } else {
00864                 tmpX = -1;
00865                 return;
00866             }
00867         }
00868         Q3TextParagraph *p = para->prev();
00869         while (p && !p->isVisible())
00870             p = p->prev();
00871         if (p)
00872             para = p;
00873         int lastLine = para->lines() - 1;
00874         if (!para->lineStartOfLine(lastLine, &indexOfLineStart))
00875             return;
00876         idx = indexOfLineStart;
00877         while (idx < para->length()-1 && para->at(idx)->x < tmpX)
00878             ++idx;
00879         if (idx > indexOfLineStart &&
00880             para->at(idx)->x - tmpX > tmpX - para->at(idx-1)->x)
00881             --idx;
00882     } else {
00883         --line;
00884         int oldIndexOfLineStart = indexOfLineStart;
00885         if (!para->lineStartOfLine(line, &indexOfLineStart))
00886             return;
00887         idx = indexOfLineStart;
00888         while (idx < oldIndexOfLineStart-1 && para->at(idx)->x < tmpX)
00889             ++idx;
00890         if (idx > indexOfLineStart &&
00891             para->at(idx)->x - tmpX > tmpX - para->at(idx-1)->x)
00892             --idx;
00893     }
00894     fixCursorPosition();
00895 }
00896 
00897 void Q3TextCursor::gotoDown()
00898 {
00899     int indexOfLineStart;
00900     int line;
00901     Q3TextStringChar *c = para->lineStartOfChar(idx, &indexOfLineStart, &line);
00902     if (!c)
00903         return;
00904 
00905     if (tmpX < 0)
00906         tmpX = x();
00907 
00908     if (line == para->lines() - 1) {
00909         if (!para->next()) {
00910             if (!nestedDepth())
00911                 return;
00912             pop();
00913             processNesting(Down);
00914             if (idx == -1) {
00915                 pop();
00916                 if (!para->next())
00917                     return;
00918                 idx = tmpX = 0;
00919             } else {
00920                 tmpX = -1;
00921                 return;
00922             }
00923         }
00924         Q3TextParagraph *s = para->next();
00925         while (s && !s->isVisible())
00926             s = s->next();
00927         if (s)
00928             para = s;
00929         if (!para->lineStartOfLine(0, &indexOfLineStart))
00930             return;
00931         int end;
00932         if (para->lines() == 1)
00933             end = para->length();
00934         else
00935             para->lineStartOfLine(1, &end);
00936 
00937         idx = indexOfLineStart;
00938         while (idx < end-1 && para->at(idx)->x < tmpX)
00939             ++idx;
00940         if (idx > indexOfLineStart &&
00941             para->at(idx)->x - tmpX > tmpX - para->at(idx-1)->x)
00942             --idx;
00943     } else {
00944         ++line;
00945         int end;
00946         if (line == para->lines() - 1)
00947             end = para->length();
00948         else
00949             para->lineStartOfLine(line + 1, &end);
00950         if (!para->lineStartOfLine(line, &indexOfLineStart))
00951             return;
00952         idx = indexOfLineStart;
00953         while (idx < end-1 && para->at(idx)->x < tmpX)
00954             ++idx;
00955         if (idx > indexOfLineStart &&
00956             para->at(idx)->x - tmpX > tmpX - para->at(idx-1)->x)
00957             --idx;
00958     }
00959     fixCursorPosition();
00960 }
00961 
00962 void Q3TextCursor::gotoLineEnd()
00963 {
00964     tmpX = -1;
00965     int indexOfLineStart;
00966     int line;
00967     Q3TextStringChar *c = para->lineStartOfChar(idx, &indexOfLineStart, &line);
00968     if (!c)
00969         return;
00970 
00971     if (line == para->lines() - 1) {
00972         idx = para->length() - 1;
00973     } else {
00974         c = para->lineStartOfLine(++line, &indexOfLineStart);
00975         indexOfLineStart--;
00976         idx = indexOfLineStart;
00977     }
00978 }
00979 
00980 void Q3TextCursor::gotoLineStart()
00981 {
00982     tmpX = -1;
00983     int indexOfLineStart;
00984     int line;
00985     Q3TextStringChar *c = para->lineStartOfChar(idx, &indexOfLineStart, &line);
00986     if (!c)
00987         return;
00988 
00989     idx = indexOfLineStart;
00990 }
00991 
00992 void Q3TextCursor::gotoHome()
00993 {
00994     if (topParagraph()->document())
00995         gotoPosition(topParagraph()->document()->firstParagraph());
00996     else
00997         gotoLineStart();
00998 }
00999 
01000 void Q3TextCursor::gotoEnd()
01001 {
01002     if (topParagraph()->document() && topParagraph()->document()->lastParagraph()->isValid())
01003         gotoPosition(topParagraph()->document()->lastParagraph(),
01004                       topParagraph()->document()->lastParagraph()->length() - 1);
01005     else
01006         gotoLineEnd();
01007 }
01008 
01009 void Q3TextCursor::gotoPageUp(int visibleHeight)
01010 {
01011     int targetY  = globalY() - visibleHeight;
01012     Q3TextParagraph* old; int index;
01013     do {
01014         old = para; index = idx;
01015         gotoUp();
01016     } while ((old != para || index != idx)  && globalY() > targetY);
01017 }
01018 
01019 void Q3TextCursor::gotoPageDown(int visibleHeight)
01020 {
01021     int targetY  = globalY() + visibleHeight;
01022     Q3TextParagraph* old; int index;
01023     do {
01024         old = para; index = idx;
01025         gotoDown();
01026     } while ((old != para || index != idx) && globalY() < targetY);
01027 }
01028 
01029 void Q3TextCursor::gotoWordRight()
01030 {
01031     if (para->string()->isRightToLeft())
01032         gotoPreviousWord();
01033     else
01034         gotoNextWord();
01035 }
01036 
01037 void Q3TextCursor::gotoWordLeft()
01038 {
01039     if (para->string()->isRightToLeft())
01040         gotoNextWord();
01041     else
01042         gotoPreviousWord();
01043 }
01044 
01045 static bool is_seperator(const QChar &c, bool onlySpace)
01046 {
01047     if (onlySpace)
01048         return c.isSpace();
01049     return c.isSpace() ||
01050         c == '\t' ||
01051         c == '.' ||
01052         c == ',' ||
01053         c == ':' ||
01054         c == ';' ||
01055         c == '-' ||
01056         c == '<' ||
01057         c == '>' ||
01058         c == '[' ||
01059         c == ']' ||
01060         c == '(' ||
01061         c == ')' ||
01062         c == '{' ||
01063         c == '}';
01064 }
01065 
01066 void Q3TextCursor::gotoPreviousWord(bool onlySpace)
01067 {
01068     gotoPreviousLetter();
01069     tmpX = -1;
01070     Q3TextString *s = para->string();
01071     bool allowSame = false;
01072     if (idx == ((int)s->length()-1))
01073         return;
01074     for (int i = idx; i >= 0; --i) {
01075         if (is_seperator(s->at(i).c, onlySpace)) {
01076             if (!allowSame)
01077                 continue;
01078             idx = i + 1;
01079             return;
01080         }
01081         if (!allowSame && !is_seperator(s->at(i).c, onlySpace))
01082             allowSame = true;
01083     }
01084     idx = 0;
01085 }
01086 
01087 void Q3TextCursor::gotoNextWord(bool onlySpace)
01088 {
01089     tmpX = -1;
01090     Q3TextString *s = para->string();
01091     bool allowSame = false;
01092     for (int i = idx; i < (int)s->length(); ++i) {
01093         if (!is_seperator(s->at(i).c, onlySpace)) {
01094             if (!allowSame)
01095                 continue;
01096             idx = i;
01097             return;
01098         }
01099         if (!allowSame && is_seperator(s->at(i).c, onlySpace))
01100             allowSame = true;
01101 
01102     }
01103 
01104     if (idx < ((int)s->length()-1)) {
01105         gotoLineEnd();
01106     } else if (para->next()) {
01107         Q3TextParagraph *p = para->next();
01108         while (p  && !p->isVisible())
01109             p = p->next();
01110         if (s) {
01111             para = p;
01112             idx = 0;
01113         }
01114     } else {
01115         gotoLineEnd();
01116     }
01117 }
01118 
01119 bool Q3TextCursor::atParagStart()
01120 {
01121     return idx == 0;
01122 }
01123 
01124 bool Q3TextCursor::atParagEnd()
01125 {
01126     return idx == para->length() - 1;
01127 }
01128 
01129 void Q3TextCursor::splitAndInsertEmptyParagraph(bool ind, bool updateIds)
01130 {
01131     if (!para->document())
01132         return;
01133     tmpX = -1;
01134     Q3TextFormat *f = 0;
01135     if (para->document()->useFormatCollection()) {
01136         f = para->at(idx)->format();
01137         if (idx == para->length() - 1 && idx > 0)
01138             f = para->at(idx - 1)->format();
01139         if (f->isMisspelled()) {
01140             f->removeRef();
01141             f = para->document()->formatCollection()->format(f->font(), f->color());
01142         }
01143     }
01144 
01145     if (atParagEnd()) {
01146         Q3TextParagraph *n = para->next();
01147         Q3TextParagraph *s = para->document()->createParagraph(para->document(), para, n, updateIds);
01148         if (f)
01149             s->setFormat(0, 1, f, true);
01150         s->copyParagData(para);
01151         if (ind) {
01152             int oi, ni;
01153             s->indent(&oi, &ni);
01154             para = s;
01155             idx = ni;
01156         } else {
01157             para = s;
01158             idx = 0;
01159         }
01160     } else if (atParagStart()) {
01161         Q3TextParagraph *p = para->prev();
01162         Q3TextParagraph *s = para->document()->createParagraph(para->document(), p, para, updateIds);
01163         if (f)
01164             s->setFormat(0, 1, f, true);
01165         s->copyParagData(para);
01166         if (ind) {
01167             s->indent();
01168             s->format();
01169             indent();
01170             para->format();
01171         }
01172     } else {
01173         QString str = para->string()->toString().mid(idx, 0xFFFFFF);
01174         Q3TextParagraph *n = para->next();
01175         Q3TextParagraph *s = para->document()->createParagraph(para->document(), para, n, updateIds);
01176         s->copyParagData(para);
01177         s->remove(0, 1);
01178         s->append(str, true);
01179         for (int i = 0; i < str.length(); ++i) {
01180             Q3TextStringChar* tsc = para->at(idx + i);
01181             s->setFormat(i, 1, tsc->format(), true);
01182 #ifndef QT_NO_TEXTCUSTOMITEM
01183             if (tsc->isCustom()) {
01184                 Q3TextCustomItem * item = tsc->customItem();
01185                 s->at(i)->setCustomItem(item);
01186                 tsc->loseCustomItem();
01187             }
01188 #endif
01189             if (tsc->isAnchor())
01190                 s->at(i)->setAnchor(tsc->anchorName(),
01191                                        tsc->anchorHref());
01192         }
01193         para->truncate(idx);
01194         if (ind) {
01195             int oi, ni;
01196             s->indent(&oi, &ni);
01197             para = s;
01198             idx = ni;
01199         } else {
01200             para = s;
01201             idx = 0;
01202         }
01203     }
01204 
01205     invalidateNested();
01206 }
01207 
01208 bool Q3TextCursor::remove()
01209 {
01210     tmpX = -1;
01211     if (!atParagEnd()) {
01212         int next = para->string()->nextCursorPosition(idx);
01213         para->remove(idx, next-idx);
01214         int h = para->rect().height();
01215         para->format(-1, true);
01216         if (h != para->rect().height())
01217             invalidateNested();
01218         else if (para->document() && para->document()->parent())
01219             para->document()->nextDoubleBuffered = true;
01220         return false;
01221     } else if (para->next()) {
01222         para->join(para->next());
01223         invalidateNested();
01224         return true;
01225     }
01226     return false;
01227 }
01228 
01229 /* needed to implement backspace the correct way */
01230 bool Q3TextCursor::removePreviousChar()
01231 {
01232     tmpX = -1;
01233     if (!atParagStart()) {
01234         para->remove(idx-1, 1);
01235         int h = para->rect().height();
01236         idx--;
01237         // shouldn't be needed, just to make sure.
01238         fixCursorPosition();
01239         para->format(-1, true);
01240         if (h != para->rect().height())
01241             invalidateNested();
01242         else if (para->document() && para->document()->parent())
01243             para->document()->nextDoubleBuffered = true;
01244         return false;
01245     } else if (para->prev()) {
01246         para = para->prev();
01247         para->join(para->next());
01248         invalidateNested();
01249         return true;
01250     }
01251     return false;
01252 }
01253 
01254 void Q3TextCursor::indent()
01255 {
01256     int oi = 0, ni = 0;
01257     para->indent(&oi, &ni);
01258     if (oi == ni)
01259         return;
01260 
01261     if (idx >= oi)
01262         idx += ni - oi;
01263     else
01264         idx = ni;
01265 }
01266 
01267 void Q3TextCursor::fixCursorPosition()
01268 {
01269     // searches for the closest valid cursor position
01270     if (para->string()->validCursorPosition(idx))
01271         return;
01272 
01273     int lineIdx;
01274     Q3TextStringChar *start = para->lineStartOfChar(idx, &lineIdx, 0);
01275     int x = para->string()->at(idx).x;
01276     int diff = QABS(start->x - x);
01277     int best = lineIdx;
01278 
01279     Q3TextStringChar *c = start;
01280     ++c;
01281 
01282     Q3TextStringChar *end = &para->string()->at(para->length()-1);
01283     while (c <= end && !c->lineStart) {
01284         int xp = c->x;
01285         if (c->rightToLeft)
01286             xp += para->string()->width(lineIdx + (c-start));
01287         int ndiff = QABS(xp - x);
01288         if (ndiff < diff && para->string()->validCursorPosition(lineIdx + (c-start))) {
01289             diff = ndiff;
01290             best = lineIdx + (c-start);
01291         }
01292         ++c;
01293     }
01294     idx = best;
01295 }
01296 
01297 
01298 // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
01299 
01300 Q3TextDocument::Q3TextDocument(Q3TextDocument *p)
01301     : par(p), parentPar(0)
01302 #ifndef QT_NO_TEXTCUSTOMITEM
01303     , tc(0)
01304 #endif
01305     , tArray(0), tStopWidth(0)
01306 {
01307     fCollection = par ? par->fCollection : new Q3TextFormatCollection;
01308     init();
01309 }
01310 
01311 void Q3TextDocument::init()
01312 {
01313     oTextValid = true;
01314     mightHaveCustomItems = false;
01315     if (par)
01316         par->insertChild(this);
01317     pProcessor = 0;
01318     useFC = true;
01319     pFormatter = 0;
01320     indenter = 0;
01321     fParag = 0;
01322     txtFormat = Qt::AutoText;
01323     preferRichText = false;
01324     pages = false;
01325     focusIndicator.parag = 0;
01326     minw = 0;
01327     wused = 0;
01328     minwParag = curParag = 0;
01329     align = Qt::AlignAuto;
01330     nSelections = 1;
01331 
01332     setStyleSheet(Q3StyleSheet::defaultSheet());
01333 #ifndef QT_NO_MIME
01334     factory_ = Q3MimeSourceFactory::defaultFactory();
01335 #endif
01336     contxt.clear();
01337 
01338     underlLinks = par ? par->underlLinks : true;
01339     backBrush = 0;
01340     buf_pixmap = 0;
01341     nextDoubleBuffered = false;
01342 
01343     if (par)
01344         withoutDoubleBuffer = par->withoutDoubleBuffer;
01345     else
01346         withoutDoubleBuffer = false;
01347 
01348     lParag = fParag = createParagraph(this, 0, 0);
01349 
01350     cx = 0;
01351     cy = 2;
01352     if (par)
01353         cx = cy = 0;
01354     cw = 600;
01355     vw = 0;
01356     flow_ = new Q3TextFlow;
01357     flow_->setWidth(cw);
01358 
01359     leftmargin = rightmargin = 4;
01360     scaleFontsFactor = 1;
01361 
01362     commandHistory = new Q3TextCommandHistory(100);
01363     tStopWidth = formatCollection()->defaultFormat()->width('x') * 8;
01364 }
01365 
01366 Q3TextDocument::~Q3TextDocument()
01367 {
01368     delete commandHistory;
01369     if (par)
01370         par->removeChild(this);
01371     clear();
01372     delete flow_;
01373     if (!par) {
01374         delete pFormatter;
01375         delete fCollection;
01376     }
01377     delete pProcessor;
01378     delete buf_pixmap;
01379     delete indenter;
01380     delete backBrush;
01381     delete [] tArray;
01382 }
01383 
01384 void Q3TextDocument::clear(bool createEmptyParag)
01385 {
01386     while (fParag) {
01387         Q3TextParagraph *p = fParag->next();
01388         delete fParag;
01389         fParag = p;
01390     }
01391     if (flow_)
01392         flow_->clear();
01393     fParag = lParag = 0;
01394     if (createEmptyParag)
01395         fParag = lParag = createParagraph(this);
01396     selections.clear();
01397     oText.clear();
01398     oTextValid = false;
01399 }
01400 
01401 int Q3TextDocument::widthUsed() const
01402 {
01403     return wused + 2*border_tolerance;
01404 }
01405 
01406 int Q3TextDocument::height() const
01407 {
01408     int h = 0;
01409     if (lParag)
01410         h = lParag->rect().top() + lParag->rect().height() + 1;
01411     int fh = flow_->boundingRect().bottom();
01412     return qMax(h, fh);
01413 }
01414 
01415 
01416 
01417 Q3TextParagraph *Q3TextDocument::createParagraph(Q3TextDocument *dc, Q3TextParagraph *pr, Q3TextParagraph *nx, bool updateIds)
01418 {
01419     return new Q3TextParagraph(dc, pr, nx, updateIds);
01420 }
01421 
01422 bool Q3TextDocument::setMinimumWidth(int needed, int used, Q3TextParagraph *p)
01423 {
01424     if (needed == -1) {
01425         minw = 0;
01426         wused = 0;
01427         p = 0;
01428     }
01429     if (p == minwParag) {
01430         if (minw > needed) {
01431             Q3TextParagraph *tp = fParag;
01432             while (tp) {
01433                 if (tp != p && tp->minwidth > needed) {
01434                     needed = tp->minwidth;
01435                     minwParag = tp;
01436                 }
01437                 tp = tp->n;
01438             }
01439         }
01440         minw = needed;
01441         emit minimumWidthChanged(minw);
01442     } else if (needed > minw) {
01443         minw = needed;
01444         minwParag = p;
01445         emit minimumWidthChanged(minw);
01446     }
01447     wused = qMax(wused, used);
01448     wused = qMax(wused, minw);
01449     cw = qMax(minw, cw);
01450     return true;
01451 }
01452 
01453 void Q3TextDocument::setPlainText(const QString &text)
01454 {
01455     preferRichText = false;
01456     clear();
01457     oTextValid = true;
01458     oText = text;
01459 
01460     int lastNl = 0;
01461     int nl = text.indexOf('\n');
01462     if (nl == -1) {
01463         lParag = createParagraph(this, lParag, 0);
01464         if (!fParag)
01465             fParag = lParag;
01466         QString s = text;
01467         if (!s.isEmpty()) {
01468             if (s[(int)s.length() - 1] == '\r')
01469                 s.remove(s.length() - 1, 1);
01470             lParag->append(s);
01471         }
01472     } else {
01473         for (;;) {
01474             lParag = createParagraph(this, lParag, 0);
01475             if (!fParag)
01476                 fParag = lParag;
01477             int l = nl - lastNl;
01478             if (l > 0) {
01479                 if (text.unicode()[nl-1] == '\r')
01480                     l--;
01481                 QString cs = QString::fromRawData(text.unicode()+lastNl, l);
01482                 lParag->append(cs);
01483             }
01484             if (nl == (int)text.length())
01485                 break;
01486             lastNl = nl + 1;
01487             nl = text.indexOf('\n', nl + 1);
01488             if (nl == -1)
01489                 nl = text.length();
01490         }
01491     }
01492     if (!lParag)
01493         lParag = fParag = createParagraph(this, 0, 0);
01494 }
01495 
01496 struct Q3TextDocumentTag {
01497     Q3TextDocumentTag(){}
01498     Q3TextDocumentTag(const QString&n, const Q3StyleSheetItem* s, const Q3TextFormat& f)
01499         :name(n),style(s), format(f), alignment(Qt::AlignAuto), direction(QChar::DirON),liststyle(Q3StyleSheetItem::ListDisc) {
01500             wsm = Q3StyleSheetItem::WhiteSpaceNormal;
01501     }
01502     QString name;
01503     const Q3StyleSheetItem* style;
01504     QString anchorHref;
01505     Q3StyleSheetItem::WhiteSpaceMode wsm;
01506     Q3TextFormat format;
01507     signed int alignment : 16;
01508     signed int direction : 5;
01509     Q3StyleSheetItem::ListStyle liststyle;
01510 
01511     Q3TextDocumentTag( const Q3TextDocumentTag& t) {
01512         name = t.name;
01513         style = t.style;
01514         anchorHref = t.anchorHref;
01515         wsm = t.wsm;
01516         format = t.format;
01517         alignment = t.alignment;
01518         direction = t.direction;
01519         liststyle = t.liststyle;
01520     }
01521     Q3TextDocumentTag& operator=(const Q3TextDocumentTag& t) {
01522         name = t.name;
01523         style = t.style;
01524         anchorHref = t.anchorHref;
01525         wsm = t.wsm;
01526         format = t.format;
01527         alignment = t.alignment;
01528         direction = t.direction;
01529         liststyle = t.liststyle;
01530         return *this;
01531     }
01532 
01533     Q_DUMMY_COMPARISON_OPERATOR(Q3TextDocumentTag)
01534 };
01535 
01536 
01537 #define NEWPAR \
01538     do{ \
01539         if (!hasNewPar) { \
01540             if (!textEditMode && curpar && curpar->length()>1 \
01541                  && curpar->at(curpar->length()-2)->c == QChar::LineSeparator) \
01542                 curpar->remove(curpar->length()-2, 1); \
01543             curpar = createParagraph(this, curpar, curpar->next()); \
01544             styles.append(vec); \
01545             vec = 0; \
01546         } \
01547         hasNewPar = true; \
01548         curpar->rtext = true;  \
01549         curpar->align = curtag.alignment; \
01550         curpar->lstyle = curtag.liststyle; \
01551         curpar->litem = (curtag.style->displayMode() == Q3StyleSheetItem::DisplayListItem); \
01552         curpar->str->setDirection((QChar::Direction)curtag.direction); \
01553         space = true; \
01554         tabExpansionColumn = 0; \
01555         delete vec; \
01556         vec = new QVector<Q3StyleSheetItem *>(); \
01557         for (QStack<Q3TextDocumentTag>::Iterator it = tags.begin(); it != tags.end(); ++it) \
01558             vec->append(const_cast<Q3StyleSheetItem *>((*it).style)); \
01559         vec->append(const_cast<Q3StyleSheetItem *>(curtag.style)); \
01560     } while(false);
01561 
01562 
01563 void Q3TextDocument::setRichText(const QString &text, const QString &context, const Q3TextFormat *initialFormat)
01564 {
01565     preferRichText = true;
01566     if (!context.isEmpty())
01567         setContext(context);
01568     clear();
01569     fParag = lParag = createParagraph(this);
01570     oTextValid = true;
01571     oText = text;
01572     setRichTextInternal(text, 0, initialFormat);
01573     fParag->rtext = true;
01574 }
01575 
01576 void Q3TextDocument::setRichTextInternal(const QString &text, Q3TextCursor* cursor, const Q3TextFormat *initialFormat)
01577 {
01578     Q3TextParagraph* curpar = lParag;
01579     int pos = 0;
01580     QStack<Q3TextDocumentTag> tags;
01581     if (!initialFormat)
01582         initialFormat = formatCollection()->defaultFormat();
01583     Q3TextDocumentTag initag("", sheet_->item(""), *initialFormat);
01584     if (bodyText.isValid())
01585         initag.format.setColor(bodyText);
01586     Q3TextDocumentTag curtag = initag;
01587     bool space = true;
01588     bool canMergeLi = false;
01589 
01590     bool textEditMode = false;
01591     int tabExpansionColumn = 0;
01592 
01593     const QChar* doc = text.unicode();
01594     int length = text.length();
01595     bool hasNewPar = curpar->length() <= 1;
01596     QString anchorName;
01597 
01598     // style sheet handling for margin and line spacing calculation below
01599     Q3TextParagraph* stylesPar = curpar;
01600     QVector<Q3StyleSheetItem *>* vec = 0;
01601     QList< QVector<Q3StyleSheetItem *> *> styles;
01602 
01603     if (cursor) {
01604         cursor->splitAndInsertEmptyParagraph();
01605         Q3TextCursor tmp = *cursor;
01606         tmp.gotoPreviousLetter();
01607         stylesPar = curpar = tmp.paragraph();
01608         hasNewPar = true;
01609         textEditMode = true;
01610     } else {
01611         NEWPAR;
01612     }
01613 
01614     // set rtext spacing to false for the initial paragraph.
01615     curpar->rtext = false;
01616 
01617     QString wellKnownTags = "br hr wsp table qt body meta title";
01618 
01619     while (pos < length) {
01620         if (hasPrefix(doc, length, pos, '<')){
01621             if (!hasPrefix(doc, length, pos+1, QChar('/'))) {
01622                 // open tag
01623                 QMap<QString, QString> attr;
01624                 QMap<QString, QString>::Iterator it, end = attr.end();
01625                 bool emptyTag = false;
01626                 QString tagname = parseOpenTag(doc, length, pos, attr, emptyTag);
01627                 if (tagname.isEmpty())
01628                     continue; // nothing we could do with this, probably parse error
01629 
01630                 const Q3StyleSheetItem* nstyle = sheet_->item(tagname);
01631 
01632                 if (nstyle) {
01633                     // we might have to close some 'forgotten' tags
01634                     while (!nstyle->allowedInContext(curtag.style)) {
01635                         QString msg;
01636                         msg.sprintf("QText Warning: Document not valid ('%s' not allowed in '%s' #%d)",
01637                                      tagname.ascii(), curtag.style->name().ascii(), pos);
01638                         sheet_->error(msg);
01639                         if (tags.isEmpty())
01640                             break;
01641                         curtag = tags.pop();
01642                     }
01643 
01644                     /* special handling for p and li for HTML
01645                        compatibility. We do not want to embed blocks in
01646                        p, and we do not want new blocks inside non-empty
01647                        lis. Plus we want to merge empty lis sometimes. */
01648                     if(nstyle->displayMode() == Q3StyleSheetItem::DisplayListItem) {
01649                         canMergeLi = true;
01650                     } else if (nstyle->displayMode() == Q3StyleSheetItem::DisplayBlock) {
01651                         while (curtag.style->name() == "p") {
01652                             if (tags.isEmpty())
01653                                 break;
01654                             curtag = tags.pop();
01655                         }
01656 
01657                         if (curtag.style->displayMode() == Q3StyleSheetItem::DisplayListItem) {
01658                             // we are in a li and a new block comes along
01659                             if (nstyle->name() == "ul" || nstyle->name() == "ol")
01660                                 hasNewPar = false; // we want an empty li (like most browsers)
01661                             if (!hasNewPar) {
01662                                 /* do not add new blocks inside
01663                                    non-empty lis */
01664                                 while (curtag.style->displayMode() == Q3StyleSheetItem::DisplayListItem) {
01665                                     if (tags.isEmpty())
01666                                         break;
01667                                     curtag = tags.pop();
01668                                 }
01669                             } else if (canMergeLi) {
01670                                 /* we have an empty li and a block
01671                                    comes along, merge them */
01672                                 nstyle = curtag.style;
01673                             }
01674                             canMergeLi = false;
01675                         }
01676                     }
01677                 }
01678 
01679 #ifndef QT_NO_TEXTCUSTOMITEM
01680                 Q3TextCustomItem* custom =  0;
01681 #else
01682                 bool custom = false;
01683 #endif
01684 
01685                 // some well-known tags, some have a nstyle, some not
01686                 if (wellKnownTags.contains(tagname)) {
01687                     if (tagname == "br") {
01688                         emptyTag = space = true;
01689                         int index = qMax(curpar->length(),1) - 1;
01690                         Q3TextFormat format = curtag.format.makeTextFormat(nstyle, attr, scaleFontsFactor);
01691                         curpar->append(QString(QChar(QChar::LineSeparator)));
01692                         curpar->setFormat(index, 1, &format);
01693                         hasNewPar = false;
01694                     }  else if (tagname == "hr") {
01695                         emptyTag = space = true;
01696 #ifndef QT_NO_TEXTCUSTOMITEM
01697                         custom = tag(sheet_, tagname, attr, contxt, *factory_ , emptyTag, this);
01698 #endif
01699                     } else if (tagname == "table") {
01700                         emptyTag = space = true;
01701 #ifndef QT_NO_TEXTCUSTOMITEM
01702                         Q3TextFormat format = curtag.format.makeTextFormat( nstyle, attr, scaleFontsFactor);
01703                         curpar->setAlignment(curtag.alignment);
01704                         custom = parseTable(attr, format, doc, length, pos, curpar);
01705 #endif
01706                     } else if (tagname == "qt" || tagname == "body") {
01707                         it = attr.find("bgcolor");
01708                         if (it != end) {
01709                             QBrush *b = new QBrush(QColor(*it));
01710                             setPaper(b);
01711                         }
01712                         it = attr.find("background");
01713                         if (it != end) {
01714 #ifndef QT_NO_MIME
01715                             QImage img;
01716                             QString bg = *it;
01717                             const QMimeSource* m = factory_->data(bg, contxt);
01718                             if (!m) {
01719                                 qCritical("QRichText: no mimesource for %s",
01720                                           QFile::encodeName(bg).data());
01721                             } else {
01722                                 if (!Q3ImageDrag::decode(m, img)) {
01723                                     qCritical("Q3TextImage: cannot decode %s",
01724                                               QFile::encodeName(bg).data());
01725                                 }
01726                             }
01727                             if (!img.isNull()) {
01728                                 QBrush *b = new QBrush(QColor(), QPixmap(img));
01729                                 setPaper(b);
01730                             }
01731 #endif
01732                         }
01733                         it = attr.find("text");
01734                         if (it != end) {
01735                             QColor c(*it);
01736                             initag.format.setColor(c);
01737                             curtag.format.setColor(c);
01738                             bodyText = c;
01739                         }
01740                         it = attr.find("link");
01741                         if (it != end)
01742                             linkColor = QColor(*it);
01743                         it = attr.find("title");
01744                         if (it != end)
01745                             attribs.insert("title", *it);
01746 
01747                         if (textEditMode) {
01748                             it = attr.find("style");
01749                             if (it != end) {
01750                                 QString a = *it;
01751                                 int count = a.count(';') + 1;
01752                                 for (int s = 0; s < count; s++) {
01753                                     QString style = a.section(';', s, s);
01754                                     if (style.startsWith("font-size:") && style.endsWith("pt")) {
01755                                         scaleFontsFactor = double(formatCollection()->defaultFormat()->fn.pointSize()) /
01756                                                            style.mid(10, style.length() - 12).toInt();
01757                                     }
01758                                 }
01759                             }
01760                             nstyle = 0; // ignore body in textEditMode
01761                         }
01762                         // end qt- and body-tag handling
01763                     } else if (tagname == "meta") {
01764                         if (attr["name"] == "qrichtext" && attr["content"] == "1")
01765                             textEditMode = true;
01766                     } else if (tagname == "title") {
01767                         QString title;
01768                         while (pos < length) {
01769                             if (hasPrefix(doc, length, pos, QChar('<')) && hasPrefix(doc, length, pos+1, QChar('/')) &&
01770                                  parseCloseTag(doc, length, pos) == "title")
01771                                 break;
01772                             title += doc[pos];
01773                             ++pos;
01774                         }
01775                         attribs.insert("title", title);
01776                     }
01777                 } // end of well-known tag handling
01778 
01779 #ifndef QT_NO_TEXTCUSTOMITEM
01780                 if (!custom) // try generic custom item
01781                     custom = tag(sheet_, tagname, attr, contxt, *factory_ , emptyTag, this);
01782 #endif
01783                 if (!nstyle && !custom) // we have no clue what this tag could be, ignore it
01784                     continue;
01785 
01786                 if (custom) {
01787 #ifndef QT_NO_TEXTCUSTOMITEM
01788                     int index = qMax(curpar->length(),1) - 1;
01789                     Q3TextFormat format = curtag.format.makeTextFormat(nstyle, attr, scaleFontsFactor);
01790                     curpar->append(QString(QChar('*')));
01791                     Q3TextFormat* f = formatCollection()->format(&format);
01792                     curpar->setFormat(index, 1, f);
01793                     curpar->at(index)->setCustomItem(custom);
01794                     if (!curtag.anchorHref.isEmpty())
01795                         curpar->at(index)->setAnchor(QString(), curtag.anchorHref);
01796                     if (!anchorName.isEmpty() ) {
01797                         curpar->at(index)->setAnchor(anchorName, curpar->at(index)->anchorHref());
01798                         anchorName.clear();
01799                     }
01800                     registerCustomItem(custom, curpar);
01801                     hasNewPar = false;
01802 #endif
01803                 } else if (!emptyTag) {
01804                     /* if we do nesting, push curtag on the stack,
01805                        otherwise reinint curag. */
01806                     if (curtag.style->name() != tagname || nstyle->selfNesting()) {
01807                         tags.push(curtag);
01808                     } else {
01809                         if (!tags.isEmpty())
01810                             curtag = tags.top();
01811                         else
01812                             curtag = initag;
01813                     }
01814 
01815                     curtag.name = tagname;
01816                     curtag.style = nstyle;
01817                     curtag.name = tagname;
01818                     curtag.style = nstyle;
01819                     if (nstyle->whiteSpaceMode()  != Q3StyleSheetItem::WhiteSpaceModeUndefined)
01820                         curtag.wsm = nstyle->whiteSpaceMode();
01821 
01822                     /* netscape compatibility: eat a newline and only a newline if a pre block starts */
01823                     if (curtag.wsm == Q3StyleSheetItem::WhiteSpacePre &&
01824                          nstyle->displayMode() == Q3StyleSheetItem::DisplayBlock)
01825                         eat(doc, length, pos, '\n');
01826 
01827                     /* ignore whitespace for inline elements if there
01828                        was already one*/
01829                     if (!textEditMode &&
01830                          (curtag.wsm == Q3StyleSheetItem::WhiteSpaceNormal
01831                           || curtag.wsm == Q3StyleSheetItem::WhiteSpaceNoWrap)
01832                          && (space || nstyle->displayMode() != Q3StyleSheetItem::DisplayInline))
01833                         eatSpace(doc, length, pos);
01834 
01835                     curtag.format = curtag.format.makeTextFormat(nstyle, attr, scaleFontsFactor);
01836                     if (nstyle->isAnchor()) {
01837                         if (!anchorName.isEmpty())
01838                             anchorName += "#" + attr["name"];
01839                         else
01840                             anchorName = attr["name"];
01841                         curtag.anchorHref = attr["href"];
01842                     }
01843 
01844                     if (nstyle->alignment() != Q3StyleSheetItem::Undefined)
01845                         curtag.alignment = nstyle->alignment();
01846 
01847                     if (nstyle->listStyle() != Q3StyleSheetItem::ListStyleUndefined)
01848                         curtag.liststyle = nstyle->listStyle();
01849 
01850                     if (nstyle->displayMode() == Q3StyleSheetItem::DisplayBlock
01851                          || nstyle->displayMode() == Q3StyleSheetItem::DisplayListItem) {
01852 
01853                         if (nstyle->name() == "ol" || nstyle->name() == "ul" || nstyle->name() == "li") {
01854                             QString type = attr["type"];
01855                             if (!type.isEmpty()) {
01856                                 if (type == "1") {
01857                                     curtag.liststyle = Q3StyleSheetItem::ListDecimal;
01858                                 } else if (type == "a") {
01859                                     curtag.liststyle = Q3StyleSheetItem::ListLowerAlpha;
01860                                 } else if (type == "A") {
01861                                     curtag.liststyle = Q3StyleSheetItem::ListUpperAlpha;
01862                                 } else {
01863                                     type = type.toLower();
01864                                     if (type == "square")
01865                                         curtag.liststyle = Q3StyleSheetItem::ListSquare;
01866                                     else if (type == "disc")
01867                                         curtag.liststyle = Q3StyleSheetItem::ListDisc;
01868                                     else if (type == "circle")
01869                                         curtag.liststyle = Q3StyleSheetItem::ListCircle;
01870                                 }
01871                             }
01872                         }
01873 
01874 
01875                         /* Internally we treat ordered and bullet
01876                           lists the same for margin calculations. In
01877                           order to have fast pointer compares in the
01878                           xMargin() functions we restrict ourselves to
01879                           <ol>. Once we calculate the margins in the
01880                           parser rathern than later, the unelegance of
01881                           this approach goes awy
01882                          */
01883                         if (nstyle->name() == "ul")
01884                             curtag.style = sheet_->item("ol");
01885 
01886                         it = attr.find("align");
01887                         if (it != end) {
01888                             QString align = (*it).toLower();
01889                             if (align == "center")
01890                                 curtag.alignment = Qt::AlignCenter;
01891                             else if (align == "right")
01892                                 curtag.alignment = Qt::AlignRight;
01893                             else if (align == "justify")
01894                                 curtag.alignment = Qt::AlignJustify;
01895                         }
01896                         it = attr.find("dir");
01897                         if (it != end) {
01898                             QString dir = (*it).toLower();
01899                             if (dir == "rtl")
01900                                 curtag.direction = QChar::DirR;
01901                             else if (dir == "ltr")
01902                                 curtag.direction = QChar::DirL;
01903                         }
01904 
01905                         NEWPAR;
01906 
01907                         if (curtag.style && curtag.style->displayMode() == Q3StyleSheetItem::DisplayListItem) {
01908                             it = attr.find("value");
01909                             if (it != end)
01910                                 curpar->setListValue((*it).toInt());
01911                         }
01912 
01913                         it = attr.find("style");
01914                         if (it != end) {
01915                             QString a = *it;
01916                             bool ok = true;
01917                             int count = a.count(';')+1;
01918                             for (int s = 0; ok && s < count; s++) {
01919                                 QString style = a.section(';', s, s);
01920                                 if (style.startsWith("margin-top:") && style.endsWith("px"))
01921                                     curpar->utm = 1+style.mid(11, style.length() - 13).toInt(&ok);
01922                                 else if (style.startsWith("margin-bottom:") && style.endsWith("px"))
01923                                     curpar->ubm = 1+style.mid(14, style.length() - 16).toInt(&ok);
01924                                 else if (style.startsWith("margin-left:") && style.endsWith("px"))
01925                                     curpar->ulm = 1+style.mid(12, style.length() - 14).toInt(&ok);
01926                                 else if (style.startsWith("margin-right:") && style.endsWith("px"))
01927                                     curpar->urm = 1+style.mid(13, style.length() - 15).toInt(&ok);
01928                                 else if (style.startsWith("text-indent:") && style.endsWith("px"))
01929                                     curpar->uflm = 1+style.mid(12, style.length() - 14).toInt(&ok);
01930                             }
01931                             if (!ok) // be pressmistic
01932                                 curpar->utm = curpar->ubm = curpar->urm = curpar->ulm = 0;
01933                         }
01934                     }
01935                 }
01936             } else {
01937                 QString tagname = parseCloseTag(doc, length, pos);
01938                 if (tagname.isEmpty())
01939                     continue; // nothing we could do with this, probably parse error
01940                 if (!sheet_->item(tagname)) // ignore unknown tags
01941                     continue;
01942                 if (tagname == "li")
01943                     continue;
01944 
01945                 // we close a block item. Since the text may continue, we need to have a new paragraph
01946                 bool needNewPar = curtag.style->displayMode() == Q3StyleSheetItem::DisplayBlock
01947                                  || curtag.style->displayMode() == Q3StyleSheetItem::DisplayListItem;
01948 
01949 
01950                 // html slopiness: handle unbalanched tag closing
01951                 while (curtag.name != tagname) {
01952                     QString msg;
01953                     msg.sprintf("QText Warning: Document not valid ('%s' not closed before '%s' #%d)",
01954                                  curtag.name.ascii(), tagname.ascii(), pos);
01955                     sheet_->error(msg);
01956                     if (tags.isEmpty())
01957                         break;
01958                     curtag = tags.pop();
01959                 }
01960 
01961 
01962                 // close the tag
01963                 if (!tags.isEmpty())
01964                     curtag = tags.pop();
01965                 else
01966                     curtag = initag;
01967 
01968                 if (needNewPar) {
01969                     if (textEditMode && (tagname == "p" || tagname == "div")) // preserve empty paragraphs
01970                         hasNewPar = false;
01971                     NEWPAR;
01972                 }
01973             }
01974         } else {
01975             // normal contents
01976             QString s;
01977             QChar c;
01978             while (pos < length && !hasPrefix(doc, length, pos, QChar('<'))){
01979                 if (textEditMode) {
01980                     // text edit mode: we handle all white space but ignore newlines
01981                     c = parseChar(doc, length, pos, Q3StyleSheetItem::WhiteSpacePre);
01982                     if (c == QChar::LineSeparator)
01983                         break;
01984                 } else {
01985                     int l = pos;
01986                     c = parseChar(doc, length, pos, curtag.wsm);
01987 
01988                     // in white space pre mode: treat any space as non breakable
01989                     // and expand tabs to eight character wide columns.
01990                     if (curtag.wsm == Q3StyleSheetItem::WhiteSpacePre) {
01991                         if  (c == '\t') {
01992                             c = ' ';
01993                             while((++tabExpansionColumn)%8)
01994                                 s += c;
01995                         }
01996                         if (c == QChar::LineSeparator)
01997                             tabExpansionColumn = 0;
01998                         else
01999                             tabExpansionColumn++;
02000 
02001                     }
02002                     if (c == ' ' || c == QChar::LineSeparator) {
02003                         /* avoid overlong paragraphs by forcing a new
02004                                paragraph after 4096 characters. This case can
02005                                occur when loading undiscovered plain text
02006                                documents in rich text mode. Instead of hanging
02007                                forever, we do the trick.
02008                             */
02009                         if (curtag.wsm == Q3StyleSheetItem::WhiteSpaceNormal && s.length() > 4096) do {
02010                             if (doc[l] == '\n') {
02011                                 hasNewPar = false; // for a new paragraph ...
02012                                 NEWPAR;
02013                                 hasNewPar = false; // ... and make it non-reusable
02014                                 c = '\n';  // make sure we break below
02015                                 break;
02016                             }
02017                         } while (++l < pos);
02018                     }
02019                 }
02020 
02021                 if (c == '\n')
02022                     break;  // break on  newlines, pre delievers a QChar::LineSeparator
02023 
02024                 bool c_isSpace = c.isSpace() && c.unicode() != 0x00a0U && !textEditMode;
02025 
02026                 if (curtag.wsm == Q3StyleSheetItem::WhiteSpaceNormal && c_isSpace && space)
02027                     continue;
02028                 if (c == '\r')
02029                     continue;
02030                 space = c_isSpace;
02031                 s += c;
02032             }
02033             if (!s.isEmpty() && curtag.style->displayMode() != Q3StyleSheetItem::DisplayNone) {
02034                 hasNewPar = false;
02035                 int index = qMax(curpar->length(),1) - 1;
02036                 curpar->append(s);
02037                 if (curtag.wsm != Q3StyleSheetItem::WhiteSpaceNormal) {
02038                     Q3TextString *str = curpar->string();
02039                     for (int i = index; i < index + s.length(); ++i)
02040                         str->at(i).nobreak = true;
02041                 }
02042 
02043                 Q3TextFormat* f = formatCollection()->format(&curtag.format);
02044                 curpar->setFormat(index, s.length(), f, false); // do not use collection because we have done that already
02045                 f->ref += s.length() -1; // that what friends are for...
02046                 if (!curtag.anchorHref.isEmpty()) {
02047                     for (int i = 0; i < int(s.length()); i++)
02048                         curpar->at(index + i)->setAnchor(QString(), curtag.anchorHref);
02049                 }
02050                 if (!anchorName.isEmpty() ) {
02051                     for (int i = 0; i < int(s.length()); i++)
02052                         curpar->at(index + i)->setAnchor(anchorName, curpar->at(index + i)->anchorHref());
02053                     anchorName.clear();
02054                 }
02055             }
02056         }
02057     }
02058 
02059     if (hasNewPar && curpar != fParag && !cursor && stylesPar != curpar) {
02060         // cleanup unused last paragraphs
02061         curpar = curpar->p;
02062         delete curpar->n;
02063     }
02064 
02065     if (!anchorName.isEmpty() ) {
02066         curpar->at(curpar->length() - 1)->setAnchor(anchorName, curpar->at(curpar->length() - 1)->anchorHref());
02067         anchorName.clear();
02068     }
02069 
02070     setRichTextMarginsInternal(styles, stylesPar);
02071 
02072     if (cursor) {
02073         cursor->gotoPreviousLetter();
02074         cursor->remove();
02075     }
02076     while (!styles.isEmpty())
02077         delete styles.takeFirst();
02078     delete vec;
02079 }
02080 
02081 void Q3TextDocument::setRichTextMarginsInternal(QList< QVector<Q3StyleSheetItem *> *>& styles, Q3TextParagraph* stylesPar)
02082 {
02083     // margin and line spacing calculation
02084     // qDebug("setRichTextMarginsInternal: styles.size() = %d", styles.size());
02085     QVector<Q3StyleSheetItem *>* prevStyle = 0;
02086     int stylesIndex = 0;
02087     QVector<Q3StyleSheetItem *>* curStyle = styles.size() ? styles.first() : 0;
02088     QVector<Q3StyleSheetItem *>* nextStyle =
02089         (++stylesIndex) < styles.size() ? styles.at(stylesIndex) : 0;
02090     while (stylesPar) {
02091         if (!curStyle) {
02092             stylesPar = stylesPar->next();
02093             prevStyle = curStyle;
02094             curStyle = nextStyle;
02095             nextStyle = (++stylesIndex) < styles.size() ? styles.at(stylesIndex) : 0;
02096             continue;
02097         }
02098 
02099         int i, mar;
02100         Q3StyleSheetItem* mainStyle = curStyle->size() ? (*curStyle)[curStyle->size()-1] : 0;
02101         if (mainStyle && mainStyle->displayMode() == Q3StyleSheetItem::DisplayListItem)
02102             stylesPar->setListItem(true);
02103         int numLists = 0;
02104         for (i = 0; i < (int)curStyle->size(); ++i) {
02105             if ((*curStyle)[i]->displayMode() == Q3StyleSheetItem::DisplayBlock
02106                  && (*curStyle)[i]->listStyle() != Q3StyleSheetItem::ListStyleUndefined)
02107                 numLists++;
02108         }
02109         stylesPar->ldepth = numLists;
02110         if (stylesPar->next() && nextStyle) {
02111             // also set the depth of the next paragraph, required for the margin calculation
02112             numLists = 0;
02113             for (i = 0; i < (int)nextStyle->size(); ++i) {
02114                 if ((*nextStyle)[i]->displayMode() == Q3StyleSheetItem::DisplayBlock
02115                      && (*nextStyle)[i]->listStyle() != Q3StyleSheetItem::ListStyleUndefined)
02116                     numLists++;
02117             }
02118             stylesPar->next()->ldepth = numLists;
02119         }
02120 
02121         // do the top margin
02122         Q3StyleSheetItem* item = mainStyle;
02123         int m;
02124         if (stylesPar->utm > 0) {
02125             m = stylesPar->utm-1;
02126             stylesPar->utm = 0;
02127         } else {
02128             m = qMax(0, item->margin(Q3StyleSheetItem::MarginTop));
02129             if (stylesPar->ldepth)
02130                 if (item->displayMode() == Q3StyleSheetItem::DisplayListItem)
02131                     m /= stylesPar->ldepth * stylesPar->ldepth;
02132                 else
02133                     m = 0;
02134         }
02135         for (i = (int)curStyle->size() - 2 ; i >= 0; --i) {
02136             item = (*curStyle)[i];
02137             if (prevStyle && i < (int) prevStyle->size() &&
02138                  ( item->displayMode() == Q3StyleSheetItem::DisplayBlock &&
02139                     (*prevStyle)[i] == item))
02140                 break;
02141             // emulate CSS2' standard 0 vertical margin for multiple ul or ol tags
02142             if (item->listStyle() != Q3StyleSheetItem::ListStyleUndefined  &&
02143                  (( i> 0 && (*curStyle)[i-1] == item) || (*curStyle)[i+1] == item))
02144                 continue;
02145             mar = qMax(0, item->margin(Q3StyleSheetItem::MarginTop));
02146             m = qMax(m, mar);
02147         }
02148         stylesPar->utm = m - stylesPar->topMargin();
02149 
02150         // do the bottom margin
02151         item = mainStyle;
02152         if (stylesPar->ubm > 0) {
02153             m = stylesPar->ubm-1;
02154             stylesPar->ubm = 0;
02155         } else {
02156             m = qMax(0, item->margin(Q3StyleSheetItem::MarginBottom));
02157             if (stylesPar->ldepth)
02158                 if (item->displayMode() == Q3StyleSheetItem::DisplayListItem)
02159                     m /= stylesPar->ldepth * stylesPar->ldepth;
02160                 else
02161                     m = 0;
02162         }
02163         for (i = (int)curStyle->size() - 2 ; i >= 0; --i) {
02164             item = (*curStyle)[i];
02165             if (nextStyle && i < (int) nextStyle->size() &&
02166                  ( item->displayMode() == Q3StyleSheetItem::DisplayBlock &&
02167                     (*nextStyle)[i] == item))
02168                 break;
02169             // emulate CSS2' standard 0 vertical margin for multiple ul or ol tags
02170             if (item->listStyle() != Q3StyleSheetItem::ListStyleUndefined  &&
02171                  (( i> 0 && (*curStyle)[i-1] == item) || (*curStyle)[i+1] == item))
02172                 continue;
02173             mar = qMax(0, item->margin(Q3StyleSheetItem::MarginBottom));
02174             m = qMax(m, mar);
02175         }
02176         stylesPar->ubm = m - stylesPar->bottomMargin();
02177 
02178         // do the left margin, simplyfied
02179         item = mainStyle;
02180         if (stylesPar->ulm > 0) {
02181             m = stylesPar->ulm-1;
02182             stylesPar->ulm = 0;
02183         } else {
02184             m = qMax(0, item->margin(Q3StyleSheetItem::MarginLeft));
02185         }
02186         for (i = (int)curStyle->size() - 2 ; i >= 0; --i) {
02187             item = (*curStyle)[i];
02188             m += qMax(0, item->margin(Q3StyleSheetItem::MarginLeft));
02189         }
02190         stylesPar->ulm = m - stylesPar->leftMargin();
02191 
02192         // do the right margin, simplyfied
02193         item = mainStyle;
02194         if (stylesPar->urm > 0) {
02195             m = stylesPar->urm-1;
02196             stylesPar->urm = 0;
02197         } else {
02198             m = qMax(0, item->margin(Q3StyleSheetItem::MarginRight));
02199         }
02200         for (i = (int)curStyle->size() - 2 ; i >= 0; --i) {
02201             item = (*curStyle)[i];
02202             m += qMax(0, item->margin(Q3StyleSheetItem::MarginRight));
02203         }
02204         stylesPar->urm = m - stylesPar->rightMargin();
02205 
02206         // do the first line margin, which really should be called text-indent
02207         item = mainStyle;
02208         if (stylesPar->uflm > 0) {
02209             m = stylesPar->uflm-1;
02210             stylesPar->uflm = 0;
02211         } else {
02212             m = qMax(0, item->margin(Q3StyleSheetItem::MarginFirstLine));
02213         }
02214         for (i = (int)curStyle->size() - 2 ; i >= 0; --i) {
02215             item = (*curStyle)[i];
02216             mar = qMax(0, item->margin(Q3StyleSheetItem::MarginFirstLine));
02217             m = qMax(m, mar);
02218         }
02219         stylesPar->uflm =m - stylesPar->firstLineMargin();
02220 
02221         // do the bogus line "spacing", which really is just an extra margin
02222         item = mainStyle;
02223         for (i = (int)curStyle->size() - 1 ; i >= 0; --i) {
02224             item = (*curStyle)[i];
02225             if (item->lineSpacing() != Q3StyleSheetItem::Undefined) {
02226                 stylesPar->ulinespacing = item->lineSpacing();
02227                 if (formatCollection() &&
02228                      stylesPar->ulinespacing < formatCollection()->defaultFormat()->height())
02229                     stylesPar->ulinespacing += formatCollection()->defaultFormat()->height();
02230                 break;
02231             }
02232         }
02233 
02234         stylesPar = stylesPar->next();
02235         prevStyle = curStyle;
02236         curStyle = nextStyle;
02237         nextStyle = (++stylesIndex) < styles.size() ? styles.at(stylesIndex) : 0;
02238     }
02239 }
02240 
02241 void Q3TextDocument::setText(const QString &text, const QString &context)
02242 {
02243     focusIndicator.parag = 0;
02244     selections.clear();
02245     if (txtFormat == Qt::AutoText && Q3StyleSheet::mightBeRichText(text) ||
02246          txtFormat == Qt::RichText)
02247         setRichText(text, context);
02248     else
02249         setPlainText(text);
02250 }
02251 
02252 QString Q3TextDocument::plainText() const
02253 {
02254     QString buffer;
02255     QString s;
02256     Q3TextParagraph *p = fParag;
02257     while (p) {
02258         if (!p->mightHaveCustomItems) {
02259             const Q3TextString *ts = p->string(); // workaround VC++ and Borland
02260             s = ts->toString(); // with false we don't fix spaces (nbsp)
02261         } else {
02262             for (int i = 0; i < p->length() - 1; ++i) {
02263 #ifndef QT_NO_TEXTCUSTOMITEM
02264                 if (p->at(i)->isCustom()) {
02265                     if (p->at(i)->customItem()->isNested()) {
02266                         s += "\n";
02267                         Q3TextTable *t = (Q3TextTable*)p->at(i)->customItem();
02268                         QList<Q3TextTableCell *> cells = t->tableCells();
02269                         for (int idx = 0; idx < cells.size(); ++idx) {
02270                             Q3TextTableCell *c = cells.at(idx);
02271                             s += c->richText()->plainText() + "\n";
02272                         }
02273                         s += "\n";
02274                     }
02275                 } else
02276 #endif
02277                 {
02278                     s += p->at(i)->c;
02279                 }
02280             }
02281         }
02282         s.remove(s.length() - 1, 1);
02283         if (p->next())
02284             s += "\n";
02285         buffer += s;
02286         p = p->next();
02287     }
02288     return buffer;
02289 }
02290 
02291 static QString align_to_string(int a)
02292 {
02293     if (a & Qt::AlignRight)
02294         return " align=\"right\"";
02295     if (a & Qt::AlignHCenter)
02296         return " align=\"center\"";
02297     if (a & Qt::AlignJustify)
02298         return " align=\"justify\"";
02299     return QString();
02300 }
02301 
02302 static QString direction_to_string(int dir)
02303 {
02304     if (dir != QChar::DirON)
02305         return (dir == QChar::DirL? " dir=\"ltr\"" : " dir=\"rtl\"");
02306     return QString();
02307 }
02308 
02309 static QString list_value_to_string(int v)
02310 {
02311     if (v != -1)
02312         return " listvalue=\"" + QString::number(v) + "\"";
02313     return QString();
02314 }
02315 
02316 static QString list_style_to_string(int v)
02317 {
02318     switch(v) {
02319     case Q3StyleSheetItem::ListDecimal: return "\"1\"";
02320     case Q3StyleSheetItem::ListLowerAlpha: return "\"a\"";
02321     case Q3StyleSheetItem::ListUpperAlpha: return "\"A\"";
02322     case Q3StyleSheetItem::ListDisc: return "\"disc\"";
02323     case Q3StyleSheetItem::ListSquare: return "\"square\"";
02324     case Q3StyleSheetItem::ListCircle: return "\"circle\"";
02325     default:
02326         return QString();
02327     }
02328 }
02329 
02330 static inline bool list_is_ordered(int v)
02331 {
02332     return v == Q3StyleSheetItem::ListDecimal ||
02333            v == Q3StyleSheetItem::ListLowerAlpha ||
02334            v == Q3StyleSheetItem::ListUpperAlpha;
02335 }
02336 
02337 
02338 static QString margin_to_string(Q3StyleSheetItem* style, int t, int b, int l, int r, int fl)
02339 {
02340     QString s;
02341     if (l > 0)
02342         s += QString(s.size() ? ";" : "") + "margin-left:" + QString::number(l+qMax(0,style->margin(Q3StyleSheetItem::MarginLeft))) + "px";
02343     if (r > 0)
02344         s += QString(s.size() ? ";" : "") + "margin-right:" + QString::number(r+qMax(0,style->margin(Q3StyleSheetItem::MarginRight))) + "px";
02345     if (t > 0)
02346         s += QString(s.size() ? ";" : "") + "margin-top:" + QString::number(t+qMax(0,style->margin(Q3StyleSheetItem::MarginTop))) + "px";
02347     if (b > 0)
02348         s += QString(s.size() ? ";" : "") + "margin-bottom:" + QString::number(b+qMax(0,style->margin(Q3StyleSheetItem::MarginBottom))) + "px";
02349     if (fl > 0)
02350         s += QString(s.size() ? ";" : "") + "text-indent:" + QString::number(fl+qMax(0,style->margin(Q3StyleSheetItem::MarginFirstLine))) + "px";
02351     if (s.size())
02352         return " style=\"" + s + "\"";
02353     return QString();
02354 }
02355 
02356 QString Q3TextDocument::richText() const
02357 {
02358     QString s = "";
02359     if (!par) {
02360         s += "<html><head><meta name=\"qrichtext\" content=\"1\" /></head><body style=\"font-size:" ;
02361         s += QString::number(formatCollection()->defaultFormat()->font().pointSize());
02362         s += "pt;font-family:";
02363         s += formatCollection()->defaultFormat()->font().family();
02364         s +="\">";
02365     }
02366     Q3TextParagraph* p = fParag;
02367 
02368     Q3StyleSheetItem* item_p = styleSheet()->item("p");
02369     Q3StyleSheetItem* item_div = styleSheet()->item("div");
02370     Q3StyleSheetItem* item_ul = styleSheet()->item("ul");
02371     Q3StyleSheetItem* item_ol = styleSheet()->item("ol");
02372     Q3StyleSheetItem* item_li = styleSheet()->item("li");
02373     if (!item_p || !item_div || !item_ul || !item_ol || !item_li) {
02374         qWarning("QTextEdit: cannot export HTML due to insufficient stylesheet (lack of p, div, ul, ol, or li)");
02375         return QString();
02376     }
02377     int pastListDepth = 0;
02378     int listDepth = 0;
02379 #if 0
02380     int futureListDepth = 0;
02381 #endif
02382     QVector<int> listStyles(10);
02383 
02384     while (p) {
02385         listDepth = p->listDepth();
02386         if (listDepth < pastListDepth)  {
02387             for (int i = pastListDepth; i > listDepth; i--)
02388                 s += list_is_ordered(listStyles[i]) ? "</ol>" : "</ul>";
02389             s += '\n';
02390         } else if (listDepth > pastListDepth) {
02391             s += '\n';
02392             listStyles.resize(qMax((int)listStyles.size(), listDepth+1));
02393             QString list_type;
02394             listStyles[listDepth] = p->listStyle();
02395             if (!list_is_ordered(p->listStyle()) || item_ol->listStyle() != p->listStyle())
02396                 list_type = " type=" + list_style_to_string(p->listStyle());
02397             for (int i = pastListDepth; i < listDepth; i++) {
02398                 s += list_is_ordered(p->listStyle()) ? "<ol" : "<ul" ;
02399                 s += list_type + ">";
02400             }
02401         } else {
02402             s += '\n';
02403         }
02404 
02405         QString ps = p->richText();
02406 
02407 #if 0
02408           // for the bottom margin we need to know whether we are at the end of a list
02409         futureListDepth = 0;
02410         if (listDepth > 0 && p->next())
02411             futureListDepth = p->next()->listDepth();
02412 #endif
02413 
02414         if (richTextExportStart && richTextExportStart->paragraph() ==p &&
02415              richTextExportStart->index() == 0)
02416             s += "<!--StartFragment-->";
02417 
02418         if (p->isListItem()) {
02419             s += "<li";
02420             if (p->listStyle() != listStyles[listDepth])
02421                 s += " type=" + list_style_to_string(p->listStyle());
02422             s +=align_to_string(p->alignment());
02423             s += margin_to_string(item_li, p->utm, p->ubm, p->ulm, p->urm, p->uflm);
02424             s +=  list_value_to_string(p->listValue());
02425             s += direction_to_string(p->direction());
02426             s +=">";
02427             s += ps;
02428             s += "</li>";
02429         } else if (p->listDepth()) {
02430             s += "<div";
02431             s += align_to_string(p->alignment());
02432             s += margin_to_string(item_div, p->utm, p->ubm, p->ulm, p->urm, p->uflm);
02433             s +=direction_to_string(p->direction());
02434             s += ">";
02435             s += ps;
02436             s += "</div>";
02437         } else {
02438             // normal paragraph item
02439             s += "<p";
02440             s += align_to_string(p->alignment());
02441             s += margin_to_string(item_p, p->utm, p->ubm, p->ulm, p->urm, p->uflm);
02442             s +=direction_to_string(p->direction());
02443             s += ">";
02444             s += ps;
02445             s += "</p>";
02446         }
02447         pastListDepth = listDepth;
02448         p = p->next();
02449     }
02450     while (listDepth > 0) {
02451         s += list_is_ordered(listStyles[listDepth]) ? "</ol>" : "</ul>";
02452         listDepth--;
02453     }
02454 
02455     if (!par)
02456         s += "\n</body></html>\n";
02457 
02458     return s;
02459 }
02460 
02461 QString Q3TextDocument::text() const
02462 {
02463     if (txtFormat == Qt::AutoText && preferRichText || txtFormat == Qt::RichText)
02464         return richText();
02465     return plainText();
02466 }
02467 
02468 QString Q3TextDocument::text(int parag) const
02469 {
02470     Q3TextParagraph *p = paragAt(parag);
02471     if (!p)
02472         return QString();
02473 
02474     if (txtFormat == Qt::AutoText && preferRichText || txtFormat == Qt::RichText)
02475         return p->richText();
02476     else
02477         return p->string()->toString();
02478 }
02479 
02480 void Q3TextDocument::invalidate()
02481 {
02482     Q3TextParagraph *s = fParag;
02483     while (s) {
02484         s->invalidate(0);
02485         s = s->next();
02486     }
02487 }
02488 
02489 void Q3TextDocument::selectionStart(int id, int &paragId, int &index)
02490 {
02491     QMap<int, Q3TextDocumentSelection>::Iterator it = selections.find(id);
02492     if (it == selections.end())
02493         return;
02494     Q3TextDocumentSelection &sel = *it;
02495     paragId = !sel.swapped ? sel.startCursor.paragraph()->paragId() : sel.endCursor.paragraph()->paragId();
02496     index = !sel.swapped ? sel.startCursor.index() : sel.endCursor.index();
02497 }
02498 
02499 Q3TextCursor Q3TextDocument::selectionStartCursor(int id)
02500 {
02501     QMap<int, Q3TextDocumentSelection>::Iterator it = selections.find(id);
02502     if (it == selections.end())
02503         return Q3TextCursor(this);
02504     Q3TextDocumentSelection &sel = *it;
02505     if (sel.swapped)
02506         return sel.endCursor;
02507     return sel.startCursor;
02508 }
02509 
02510 Q3TextCursor Q3TextDocument::selectionEndCursor(int id)
02511 {
02512     QMap<int, Q3TextDocumentSelection>::Iterator it = selections.find(id);
02513     if (it == selections.end())
02514         return Q3TextCursor(this);
02515     Q3TextDocumentSelection &sel = *it;
02516     if (!sel.swapped)
02517         return sel.endCursor;
02518     return sel.startCursor;
02519 }
02520 
02521 void Q3TextDocument::selectionEnd(int id, int &paragId, int &index)
02522 {
02523     QMap<int, Q3TextDocumentSelection>::Iterator it = selections.find(id);
02524     if (it == selections.end())
02525         return;
02526     Q3TextDocumentSelection &sel = *it;
02527     paragId = sel.swapped ? sel.startCursor.paragraph()->paragId() : sel.endCursor.paragraph()->paragId();
02528     index = sel.swapped ? sel.startCursor.index() : sel.endCursor.index();
02529 }
02530 
02531 void Q3TextDocument::addSelection(int id)
02532 {
02533     nSelections = qMax(nSelections, id + 1);
02534 }
02535 
02536 static void setSelectionEndHelper(int id, Q3TextDocumentSelection &sel, Q3TextCursor &start, Q3TextCursor &end)
02537 {
02538     Q3TextCursor c1 = start;
02539     Q3TextCursor c2 = end;
02540     if (sel.swapped) {
02541         c1 = end;
02542         c2 = start;
02543     }
02544 
02545     c1.paragraph()->removeSelection(id);
02546     c2.paragraph()->removeSelection(id);
02547     if (c1.paragraph() != c2.paragraph()) {
02548         c1.paragraph()->setSelection(id, c1.index(), c1.paragraph()->length() - 1);
02549         c2.paragraph()->setSelection(id, 0, c2.index());
02550     } else {
02551         c1.paragraph()->setSelection(id, qMin(c1.index(), c2.index()), qMax(c1.index(), c2.index()));
02552     }
02553 
02554     sel.startCursor = start;
02555     sel.endCursor = end;
02556     if (sel.startCursor.paragraph() == sel.endCursor.paragraph())
02557         sel.swapped = sel.startCursor.index() > sel.endCursor.index();
02558 }
02559 
02560 bool Q3TextDocument::setSelectionEnd(int id, const Q3TextCursor &cursor)
02561 {
02562     QMap<int, Q3TextDocumentSelection>::Iterator it = selections.find(id);
02563     if (it == selections.end())
02564         return false;
02565     Q3TextDocumentSelection &sel = *it;
02566 
02567     Q3TextCursor start = sel.startCursor;
02568     Q3TextCursor end = cursor;
02569 
02570     if (start == end) {
02571         removeSelection(id);
02572         setSelectionStart(id, cursor);
02573         return true;
02574     }
02575 
02576     if (sel.endCursor.paragraph() == end.paragraph()) {
02577         setSelectionEndHelper(id, sel, start, end);
02578         return true;
02579     }
02580 
02581     bool inSelection = false;
02582     Q3TextCursor c(this);
02583     Q3TextCursor tmp = sel.startCursor;
02584     if (sel.swapped)
02585         tmp = sel.endCursor;
02586     tmp.restoreState();
02587     Q3TextCursor tmp2 = cursor;
02588     tmp2.restoreState();
02589     c.setParagraph(tmp.paragraph()->paragId() < tmp2.paragraph()->paragId() ? tmp.paragraph() : tmp2.paragraph());
02590     bool hadStart = false;
02591     bool hadEnd = false;
02592     bool hadStartParag = false;
02593     bool hadEndParag = false;
02594     bool hadOldStart = false;
02595     bool hadOldEnd = false;
02596     bool leftSelection = false;
02597     sel.swapped = false;
02598     for (;;) {
02599         if (c == start)
02600             hadStart = true;
02601         if (c == end)
02602             hadEnd = true;
02603         if (c.paragraph() == start.paragraph())
02604             hadStartParag = true;
02605         if (c.paragraph() == end.paragraph())
0260