diff options
Diffstat (limited to 'src/qt3support/text')
21 files changed, 25840 insertions, 0 deletions
diff --git a/src/qt3support/text/q3multilineedit.cpp b/src/qt3support/text/q3multilineedit.cpp new file mode 100644 index 0000000..23b9776 --- /dev/null +++ b/src/qt3support/text/q3multilineedit.cpp @@ -0,0 +1,535 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <qplatformdefs.h> +#include "q3multilineedit.h" +#ifndef QT_NO_MULTILINEEDIT +#include "qpainter.h" +#include "qscrollbar.h" +#include "qcursor.h" +#include "qclipboard.h" +#include "qpixmap.h" +#include "qregexp.h" +#include "qapplication.h" +#include "q3dragobject.h" +#include "qtimer.h" +#include <private/q3richtext_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \class Q3MultiLineEdit + + \brief The Q3MultiLineEdit widget is a simple editor for inputting text. + + \compat + + The Q3MultiLineEdit was a simple editor widget in former Qt versions. Qt + 3.0 includes a new richtext engine which obsoletes Q3MultiLineEdit. It is + still included for compatibility reasons. It is now a subclass of + \l Q3TextEdit, and provides enough of the old Q3MultiLineEdit API to keep old + applications working. + + If you implement something new with Q3MultiLineEdit, we suggest using + \l Q3TextEdit instead and call Q3TextEdit::setTextFormat(Qt::PlainText). + + Although most of the old Q3MultiLineEdit API is still available, there is + a few difference. The old Q3MultiLineEdit operated on lines, not on + paragraphs. As lines change all the time during wordwrap, the new + richtext engine uses paragraphs as basic elements in the data structure. + All functions (numLines(), textLine(), etc.) that operated on lines, now + operate on paragraphs. Further, getString() has been removed completely. + It revealed too much of the internal data structure. + + Applications which made normal and reasonable use of Q3MultiLineEdit + should still work without problems. Some odd usage will require some + porting. In these cases, it may be better to use \l Q3TextEdit now. + + \sa Q3TextEdit +*/ + +/*! + \fn bool Q3MultiLineEdit::autoUpdate() const + + This function is a noop that always returns true. +*/ + +/*! + \fn virtual void Q3MultiLineEdit::setAutoUpdate(bool b) + + \internal +*/ + +/*! + \fn int Q3MultiLineEdit::totalWidth() const +*/ + +/*! + \fn int Q3MultiLineEdit::totalHeight() const +*/ + +/*! + \fn int Q3MultiLineEdit::maxLines() const +*/ + +/*! + \fn void Q3MultiLineEdit::setMaxLines(int max) + + Sets the maximum number of lines this Q3MultiLineEdit will hold to + \a max. +*/ + +/*! + \fn void Q3MultiLineEdit::deselect() +*/ + + +class Q3MultiLineEditData +{ +}; + + +/*! + Constructs a new, empty, Q3MultiLineEdit with parent \a parent called + \a name. +*/ + +Q3MultiLineEdit::Q3MultiLineEdit(QWidget *parent , const char *name) + : Q3TextEdit(parent, name) +{ + d = new Q3MultiLineEditData; + setTextFormat(Qt::PlainText); +} + +/*! \property Q3MultiLineEdit::numLines + \brief the number of paragraphs in the editor + + The count includes any empty paragraph at top and bottom, so for an + empty editor this method returns 1. +*/ + +int Q3MultiLineEdit::numLines() const +{ + return document()->lastParagraph()->paragId() + 1; +} + +/*! \property Q3MultiLineEdit::atEnd + \brief whether the cursor is placed at the end of the text + + \sa atBeginning +*/ + +bool Q3MultiLineEdit::atEnd() const +{ + return textCursor()->paragraph() == document()->lastParagraph() && textCursor()->atParagEnd(); +} + + +/*! \property Q3MultiLineEdit::atBeginning + \brief whether the cursor is placed at the beginning of the text + + \sa atEnd +*/ + +bool Q3MultiLineEdit::atBeginning() const +{ + return textCursor()->paragraph() == document()->firstParagraph() && textCursor()->atParagStart(); +} + +/*! Returns the number of characters at paragraph number \a row. If + \a row is out of range, -1 is returned. +*/ + +int Q3MultiLineEdit::lineLength(int row) const +{ + if (row < 0 || row > numLines()) + return -1; + return document()->paragAt(row)->length() - 1; +} + + +/*! Destructor. */ + +Q3MultiLineEdit::~Q3MultiLineEdit() +{ + delete d; +} + +/*! + If there is selected text, sets \a line1, \a col1, \a line2 and \a col2 + to the start and end of the selected region and returns true. Returns + false if there is no selected text. + */ +bool Q3MultiLineEdit::getMarkedRegion(int *line1, int *col1, + int *line2, int *col2) const +{ + int p1,c1, p2, c2; + getSelection(&p1, &c1, &p2, &c2); + if (p1 == -1 && c1 == -1 && p2 == -1 && c2 == -1) + return false; + if (line1) + *line1 = p1; + if (col1) + *col1 = c1; + if (line2) + *line2 = p2; + if (col2) + *col2 = c2; + return true; +} + + +/*! + Returns true if there is selected text. +*/ + +bool Q3MultiLineEdit::hasMarkedText() const +{ + return hasSelectedText(); +} + + +/*! + Returns a copy of the selected text. +*/ + +QString Q3MultiLineEdit::markedText() const +{ + return selectedText(); +} + +/*! + Moves the cursor one page down. If \a mark is true, the text + is selected. +*/ + +void Q3MultiLineEdit::pageDown(bool mark) +{ + moveCursor(MoveDown, mark); +} + + +/*! + Moves the cursor one page up. If \a mark is true, the text + is selected. +*/ + +void Q3MultiLineEdit::pageUp(bool mark) +{ + moveCursor(MovePgUp, mark); +} + + +/*! Inserts \a txt at paragraph number \a line. If \a line is less + than zero, or larger than the number of paragraphs, the new text is + put at the end. If \a txt contains newline characters, several + paragraphs are inserted. + + The cursor position is not changed. +*/ + +void Q3MultiLineEdit::insertLine(const QString &txt, int line) +{ + insertParagraph(txt, line); +} + +/*! Deletes the paragraph at paragraph number \a paragraph. If \a + paragraph is less than zero or larger than the number of paragraphs, + nothing is deleted. +*/ + +void Q3MultiLineEdit::removeLine(int paragraph) +{ + removeParagraph(paragraph); +} + +/*! Inserts \a str at the current cursor position and selects the + text if \a mark is true. +*/ + +void Q3MultiLineEdit::insertAndMark(const QString& str, bool mark) +{ + insert(str); + if (mark) + document()->setSelectionEnd(Q3TextDocument::Standard, *textCursor()); +} + +/*! Splits the paragraph at the current cursor position. +*/ + +void Q3MultiLineEdit::newLine() +{ + insert(QString(QLatin1Char('\n'))); +} + + +/*! Deletes the character on the left side of the text cursor and + moves the cursor one position to the left. If a text has been selected + by the user (e.g. by clicking and dragging) the cursor is put at the + beginning of the selected text and the selected text is removed. \sa + del() +*/ + +void Q3MultiLineEdit::backspace() +{ + if (document()->hasSelection(Q3TextDocument::Standard)) { + removeSelectedText(); + return; + } + + if (!textCursor()->paragraph()->prev() && + textCursor()->atParagStart()) + return; + + doKeyboardAction(ActionBackspace); +} + + +/*! Moves the text cursor to the left end of the line. If \a mark is + true, text is selected toward the first position. If it is false and the + cursor is moved, all selected text is unselected. + + \sa end() +*/ + +void Q3MultiLineEdit::home(bool mark) +{ + moveCursor(MoveLineStart, mark); +} + +/*! Moves the text cursor to the right end of the line. If \a mark is + true, text is selected toward the last position. If it is false and the + cursor is moved, all selected text is unselected. + + \sa home() +*/ + +void Q3MultiLineEdit::end(bool mark) +{ + moveCursor(MoveLineEnd, mark); +} + + +/*! + \fn void Q3MultiLineEdit::setCursorPosition(int line, int col) + \reimp +*/ + +/*! Sets the cursor position to character number \a col in paragraph + number \a line. The parameters are adjusted to lie within the legal + range. + + If \a mark is false, the selection is cleared. otherwise it is extended. + +*/ + +void Q3MultiLineEdit::setCursorPosition(int line, int col, bool mark) +{ + if (!mark) + selectAll(false); + Q3TextEdit::setCursorPosition(line, col); + if (mark) + document()->setSelectionEnd(Q3TextDocument::Standard, *textCursor()); +} + +/*! Returns the top center point where the cursor is drawn. +*/ + +QPoint Q3MultiLineEdit::cursorPoint() const +{ + return QPoint(textCursor()->x(), textCursor()->y() + textCursor()->paragraph()->rect().y()); +} + +/*! \property Q3MultiLineEdit::alignment + \brief The editor's paragraph alignment + + Sets the alignment to flag, which must be Qt::AlignLeft, + Qt::AlignHCenter, or \c Qt::AlignRight. + + If flag is an illegal flag, nothing happens. +*/ +void Q3MultiLineEdit::setAlignment(Qt::Alignment flag) +{ + if (flag == Qt::AlignCenter) + flag = Qt::AlignHCenter; + if (flag != Qt::AlignLeft && flag != Qt::AlignRight && flag != Qt::AlignHCenter) + return; + Q3TextParagraph *p = document()->firstParagraph(); + while (p) { + p->setAlignment(flag); + p = p->next(); + } +} + +Qt::Alignment Q3MultiLineEdit::alignment() const +{ + return QFlag(document()->firstParagraph()->alignment()); +} + + +void Q3MultiLineEdit::setEdited(bool e) +{ + setModified(e); +} + +/*! \property Q3MultiLineEdit::edited + \brief whether the document has been edited by the user + + This is the same as Q3TextEdit's "modifed" property. +*/ +bool Q3MultiLineEdit::edited() const +{ + return isModified(); +} + +/*! Moves the cursor one word to the right. If \a mark is true, the text + is selected. + + \sa cursorWordBackward() +*/ +void Q3MultiLineEdit::cursorWordForward(bool mark) +{ + moveCursor(MoveWordForward, mark); +} + +/*! Moves the cursor one word to the left. If \a mark is true, the + text is selected. + + \sa cursorWordForward() +*/ +void Q3MultiLineEdit::cursorWordBackward(bool mark) +{ + moveCursor(MoveWordBackward, mark); +} + +/*! + \fn Q3MultiLineEdit::insertAt(const QString &s, int line, int col) + \reimp +*/ + +/*! Inserts string \a s at paragraph number \a line, after character + number \a col in the paragraph. If \a s contains newline + characters, new lines are inserted. + If \a mark is true the inserted string will be selected. + + The cursor position is adjusted. + */ + +void Q3MultiLineEdit::insertAt(const QString &s, int line, int col, bool mark) +{ + Q3TextEdit::insertAt(s, line, col); + if (mark) + setSelection(line, col, line, col + s.length()); +} + +// ### reggie - is this documentation correct? + +/*! Deletes text from the current cursor position to the end of the + line. (Note that this function still operates on lines, not paragraphs.) +*/ + +void Q3MultiLineEdit::killLine() +{ + doKeyboardAction(ActionKill); +} + +/*! Moves the cursor one character to the left. If \a mark is true, + the text is selected. + The \a wrap parameter is currently ignored. + + \sa cursorRight() cursorUp() cursorDown() +*/ + +void Q3MultiLineEdit::cursorLeft(bool mark, bool) +{ + moveCursor(MoveBackward, mark); +} + +/*! Moves the cursor one character to the right. If \a mark is true, + the text is selected. + The \a wrap parameter is currently ignored. + + \sa cursorLeft() cursorUp() cursorDown() +*/ + +void Q3MultiLineEdit::cursorRight(bool mark, bool) +{ + moveCursor(MoveForward, mark); +} + +/*! Moves the cursor up one line. If \a mark is true, the text is + selected. + + \sa cursorDown() cursorLeft() cursorRight() +*/ + +void Q3MultiLineEdit::cursorUp(bool mark) +{ + moveCursor(MoveUp, mark); +} + +/*! + Moves the cursor one line down. If \a mark is true, the text + is selected. + \sa cursorUp() cursorLeft() cursorRight() +*/ + +void Q3MultiLineEdit::cursorDown(bool mark) +{ + moveCursor(MoveDown, mark); +} + + +/*! Returns the text at line number \a line (possibly the empty + string), or a null if \a line is invalid. +*/ + +QString Q3MultiLineEdit::textLine(int line) const +{ + if (line < 0 || line >= numLines()) + return QString(); + QString str = document()->paragAt(line)->string()->toString(); + str.truncate(str.length() - 1); + return str; +} + +QT_END_NAMESPACE + +#endif diff --git a/src/qt3support/text/q3multilineedit.h b/src/qt3support/text/q3multilineedit.h new file mode 100644 index 0000000..d97e548 --- /dev/null +++ b/src/qt3support/text/q3multilineedit.h @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3MULTILINEEDIT_H +#define Q3MULTILINEEDIT_H + +#include <Qt3Support/q3textedit.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3SupportLight) + +#ifndef QT_NO_MULTILINEEDIT + +class Q3MultiLineEditCommand; +class QValidator; +class Q3MultiLineEditData; + +class Q_COMPAT_EXPORT Q3MultiLineEdit : public Q3TextEdit +{ + Q_OBJECT + Q_PROPERTY(int numLines READ numLines) + Q_PROPERTY(bool atBeginning READ atBeginning) + Q_PROPERTY(bool atEnd READ atEnd) + Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment) + Q_PROPERTY(bool edited READ edited WRITE setEdited DESIGNABLE false) + +public: + Q3MultiLineEdit(QWidget* parent=0, const char* name=0); + ~Q3MultiLineEdit(); + + QString textLine(int line) const; + int numLines() const; + + virtual void insertLine(const QString &s, int line = -1); + virtual void insertAt(const QString &s, int line, int col) { + insertAt(s, line, col, false); + } + virtual void insertAt(const QString &s, int line, int col, bool mark); + virtual void removeLine(int line); + virtual void setCursorPosition(int line, int col) { + setCursorPosition(line, col, false); + } + virtual void setCursorPosition(int line, int col, bool mark); + bool atBeginning() const; + bool atEnd() const; + + void setAlignment(Qt::Alignment flags); + Qt::Alignment alignment() const; + + void setEdited(bool); + bool edited() const; + + bool hasMarkedText() const; + QString markedText() const; + + void cursorWordForward(bool mark); + void cursorWordBackward(bool mark); + + // noops + bool autoUpdate() const { return true; } + virtual void setAutoUpdate(bool) {} + + int totalWidth() const { return contentsWidth(); } + int totalHeight() const { return contentsHeight(); } + + int maxLines() const { return QWIDGETSIZE_MAX; } + void setMaxLines(int) {} + +public Q_SLOTS: + void deselect() { selectAll(false); } + +protected: + QPoint cursorPoint() const; + virtual void insertAndMark(const QString&, bool mark); + virtual void newLine(); + virtual void killLine(); + virtual void pageUp(bool mark=false); + virtual void pageDown(bool mark=false); + virtual void cursorLeft(bool mark=false, bool wrap = true); + virtual void cursorRight(bool mark=false, bool wrap = true); + virtual void cursorUp(bool mark=false); + virtual void cursorDown(bool mark=false); + virtual void backspace(); + virtual void home(bool mark=false); + virtual void end(bool mark=false); + + bool getMarkedRegion(int *line1, int *col1, int *line2, int *col2) const; + int lineLength(int row) const; + +private: + Q_DISABLE_COPY(Q3MultiLineEdit) + + Q3MultiLineEditData *d; +}; + +#endif // QT_NO_MULTILINEEDIT + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3MULTILINEEDIT_H diff --git a/src/qt3support/text/q3richtext.cpp b/src/qt3support/text/q3richtext.cpp new file mode 100644 index 0000000..c058e37 --- /dev/null +++ b/src/qt3support/text/q3richtext.cpp @@ -0,0 +1,8353 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3richtext_p.h" + +#ifndef QT_NO_RICHTEXT + +#include "qbitmap.h" +#include "qapplication.h" +#include "q3cleanuphandler.h" +#include "qcursor.h" +#include "qdatastream.h" +#include "q3dragobject.h" +#include "qdrawutil.h" +#include "qfile.h" +#include "qfileinfo.h" +#include "qfont.h" +#include "qimage.h" +#include "qmap.h" +#include "qmime.h" +#include "q3paintdevicemetrics.h" +#include "qpainter.h" +#include "qstringlist.h" +#include "qstyle.h" +#include "qstyleoption.h" +#include "q3stylesheet.h" +#include "qtextstream.h" +#include <private/qtextengine_p.h> +#include <private/qunicodetables_p.h> + +#include <stdlib.h> + +#if defined(Q_WS_X11) +#include "qx11info_x11.h" +#endif + +QT_BEGIN_NAMESPACE + +static Q3TextCursor* richTextExportStart = 0; +static Q3TextCursor* richTextExportEnd = 0; + +class Q3TextFormatCollection; + +const int border_tolerance = 2; + +#ifdef Q_WS_WIN +QT_BEGIN_INCLUDE_NAMESPACE +#include "qt_windows.h" +QT_END_INCLUDE_NAMESPACE +#endif + +static inline bool is_printer(QPainter *p) +{ + if (!p || !p->device()) + return false; + return p->device()->devType() == QInternal::Printer; +} + +static inline int scale(int value, QPainter *painter) +{ + if (is_printer(painter)) { + Q3PaintDeviceMetrics metrics(painter->device()); +#if defined(Q_WS_X11) + value = value * metrics.logicalDpiY() / + QX11Info::appDpiY(painter->device()->x11Screen()); +#elif defined (Q_WS_WIN) + HDC hdc = GetDC(0); + int gdc = GetDeviceCaps(hdc, LOGPIXELSY); + if (gdc) + value = value * metrics.logicalDpiY() / gdc; + ReleaseDC(0, hdc); +#elif defined (Q_WS_MAC) + value = value * metrics.logicalDpiY() / 75; // ##### FIXME +#elif defined (Q_WS_QWS) + value = value * metrics.logicalDpiY() / 75; +#endif + } + return value; +} + + +static inline bool isBreakable(Q3TextString *string, int pos) +{ + if (string->at(pos).nobreak) + return false; + return (pos < string->length()-1 && string->at(pos+1).softBreak); +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +void Q3TextCommandHistory::addCommand(Q3TextCommand *cmd) +{ + if (current < history.count() - 1) { + QList<Q3TextCommand *> commands; + + for (int i = 0; i <= current; ++i) + commands.insert(i, history.takeFirst()); + + commands.append(cmd); + while (!history.isEmpty()) + delete history.takeFirst(); + history = commands; + } else { + history.append(cmd); + } + + if (history.count() > steps) + delete history.takeFirst(); + else + ++current; +} + +Q3TextCursor *Q3TextCommandHistory::undo(Q3TextCursor *c) +{ + if (current > -1) { + Q3TextCursor *c2 = history.at(current)->unexecute(c); + --current; + return c2; + } + return 0; +} + +Q3TextCursor *Q3TextCommandHistory::redo(Q3TextCursor *c) +{ + if (current > -1) { + if (current < history.count() - 1) { + ++current; + return history.at(current)->execute(c); + } + } else { + if (history.count() > 0) { + ++current; + return history.at(current)->execute(c); + } + } + return 0; +} + +bool Q3TextCommandHistory::isUndoAvailable() +{ + return current > -1; +} + +bool Q3TextCommandHistory::isRedoAvailable() +{ + return (current > -1 && current < history.count() - 1) || (current == -1 && history.count() > 0); +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Q3TextDeleteCommand::Q3TextDeleteCommand(Q3TextDocument *dc, int i, int idx, const QVector<Q3TextStringChar> &str, + const QByteArray& oldStyleInfo) + : Q3TextCommand(dc), id(i), index(idx), parag(0), text(str), styleInformation(oldStyleInfo) +{ + for (int j = 0; j < (int)text.size(); ++j) { + if (text[j].format()) + text[j].format()->addRef(); + } +} + +Q3TextDeleteCommand::Q3TextDeleteCommand(Q3TextParagraph *p, int idx, const QVector<Q3TextStringChar> &str) + : Q3TextCommand(0), id(-1), index(idx), parag(p), text(str) +{ + for (int i = 0; i < (int)text.size(); ++i) { + if (text[i].format()) + text[i].format()->addRef(); + } +} + +Q3TextDeleteCommand::~Q3TextDeleteCommand() +{ + for (int i = 0; i < (int)text.size(); ++i) { + if (text[i].format()) + text[i].format()->removeRef(); + } + text.resize(0); +} + +Q3TextCursor *Q3TextDeleteCommand::execute(Q3TextCursor *c) +{ + Q3TextParagraph *s = doc ? doc->paragAt(id) : parag; + if (!s) { + qWarning("can't locate parag at %d, last parag: %d", id, doc->lastParagraph()->paragId()); + return 0; + } + + cursor.setParagraph(s); + cursor.setIndex(index); + int len = text.size(); + if (c) + *c = cursor; + if (doc) { + doc->setSelectionStart(Q3TextDocument::Temp, cursor); + for (int i = 0; i < len; ++i) + cursor.gotoNextLetter(); + doc->setSelectionEnd(Q3TextDocument::Temp, cursor); + doc->removeSelectedText(Q3TextDocument::Temp, &cursor); + if (c) + *c = cursor; + } else { + s->remove(index, len); + } + + return c; +} + +Q3TextCursor *Q3TextDeleteCommand::unexecute(Q3TextCursor *c) +{ + Q3TextParagraph *s = doc ? doc->paragAt(id) : parag; + if (!s) { + qWarning("can't locate parag at %d, last parag: %d", id, doc->lastParagraph()->paragId()); + return 0; + } + + cursor.setParagraph(s); + cursor.setIndex(index); + QString str = Q3TextString::toString(text); + cursor.insert(str, true, &text); + if (c) + *c = cursor; + cursor.setParagraph(s); + cursor.setIndex(index); + +#ifndef QT_NO_DATASTREAM + if (!styleInformation.isEmpty()) { + QDataStream styleStream(&styleInformation, IO_ReadOnly); + int num; + styleStream >> num; + Q3TextParagraph *p = s; + while (num-- && p) { + p->readStyleInformation(styleStream); + p = p->next(); + } + } +#endif + s = cursor.paragraph(); + while (s) { + s->format(); + s->setChanged(true); + if (s == c->paragraph()) + break; + s = s->next(); + } + + return &cursor; +} + +Q3TextFormatCommand::Q3TextFormatCommand(Q3TextDocument *dc, int sid, int sidx, int eid, int eidx, + const QVector<Q3TextStringChar> &old, Q3TextFormat *f, int fl) + : Q3TextCommand(dc), startId(sid), startIndex(sidx), endId(eid), endIndex(eidx), format(f), oldFormats(old), flags(fl) +{ + format = dc->formatCollection()->format(f); + for (int j = 0; j < (int)oldFormats.size(); ++j) { + if (oldFormats[j].format()) + oldFormats[j].format()->addRef(); + } +} + +Q3TextFormatCommand::~Q3TextFormatCommand() +{ + format->removeRef(); + for (int j = 0; j < (int)oldFormats.size(); ++j) { + if (oldFormats[j].format()) + oldFormats[j].format()->removeRef(); + } +} + +Q3TextCursor *Q3TextFormatCommand::execute(Q3TextCursor *c) +{ + Q3TextParagraph *sp = doc->paragAt(startId); + Q3TextParagraph *ep = doc->paragAt(endId); + if (!sp || !ep) + return c; + + Q3TextCursor start(doc); + start.setParagraph(sp); + start.setIndex(startIndex); + Q3TextCursor end(doc); + end.setParagraph(ep); + end.setIndex(endIndex); + + doc->setSelectionStart(Q3TextDocument::Temp, start); + doc->setSelectionEnd(Q3TextDocument::Temp, end); + doc->setFormat(Q3TextDocument::Temp, format, flags); + doc->removeSelection(Q3TextDocument::Temp); + if (endIndex == ep->length()) + end.gotoLeft(); + *c = end; + return c; +} + +Q3TextCursor *Q3TextFormatCommand::unexecute(Q3TextCursor *c) +{ + Q3TextParagraph *sp = doc->paragAt(startId); + Q3TextParagraph *ep = doc->paragAt(endId); + if (!sp || !ep) + return 0; + + int idx = startIndex; + int fIndex = 0; + while ( fIndex < int(oldFormats.size()) ) { + if (oldFormats.at(fIndex).c == QLatin1Char('\n')) { + if (idx > 0) { + if (idx < sp->length() && fIndex > 0) + sp->setFormat(idx, 1, oldFormats.at(fIndex - 1).format()); + if (sp == ep) + break; + sp = sp->next(); + idx = 0; + } + fIndex++; + } + if (oldFormats.at(fIndex).format()) + sp->setFormat(idx, 1, oldFormats.at(fIndex).format()); + idx++; + fIndex++; + if (fIndex >= (int)oldFormats.size()) + break; + if (idx >= sp->length()) { + if (sp == ep) + break; + sp = sp->next(); + idx = 0; + } + } + + Q3TextCursor end(doc); + end.setParagraph(ep); + end.setIndex(endIndex); + if (endIndex == ep->length()) + end.gotoLeft(); + *c = end; + return c; +} + +Q3TextStyleCommand::Q3TextStyleCommand(Q3TextDocument *dc, int fParag, int lParag, const QByteArray& beforeChange) + : Q3TextCommand(dc), firstParag(fParag), lastParag(lParag), before(beforeChange) +{ + after = readStyleInformation( dc, fParag, lParag); +} + + +QByteArray Q3TextStyleCommand::readStyleInformation( Q3TextDocument* doc, int fParag, int lParag) +{ + QByteArray style; +#ifndef QT_NO_DATASTREAM + Q3TextParagraph *p = doc->paragAt(fParag); + if (!p) + return style; + QDataStream styleStream(&style, IO_WriteOnly); + int num = lParag - fParag + 1; + styleStream << num; + while (num -- && p) { + p->writeStyleInformation(styleStream); + p = p->next(); + } +#endif + return style; +} + +void Q3TextStyleCommand::writeStyleInformation( Q3TextDocument* doc, int fParag, const QByteArray& style) +{ +#ifndef QT_NO_DATASTREAM + Q3TextParagraph *p = doc->paragAt(fParag); + if (!p) + return; + QByteArray copy = style; + QDataStream styleStream(©, IO_ReadOnly); + int num; + styleStream >> num; + while (num-- && p) { + p->readStyleInformation(styleStream); + p = p->next(); + } +#endif +} + +Q3TextCursor *Q3TextStyleCommand::execute(Q3TextCursor *c) +{ + writeStyleInformation(doc, firstParag, after); + return c; +} + +Q3TextCursor *Q3TextStyleCommand::unexecute(Q3TextCursor *c) +{ + writeStyleInformation(doc, firstParag, before); + return c; +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Q3TextCursor::Q3TextCursor(Q3TextDocument *dc) + : idx(0), tmpX(-1), ox(0), oy(0), + valid(true) +{ + para = dc ? dc->firstParagraph() : 0; +} + +Q3TextCursor::Q3TextCursor(const Q3TextCursor &c) +{ + ox = c.ox; + oy = c.oy; + idx = c.idx; + para = c.para; + tmpX = c.tmpX; + indices = c.indices; + paras = c.paras; + xOffsets = c.xOffsets; + yOffsets = c.yOffsets; + valid = c.valid; +} + +Q3TextCursor::~Q3TextCursor() +{ +} + +Q3TextCursor &Q3TextCursor::operator=(const Q3TextCursor &c) +{ + ox = c.ox; + oy = c.oy; + idx = c.idx; + para = c.para; + tmpX = c.tmpX; + indices = c.indices; + paras = c.paras; + xOffsets = c.xOffsets; + yOffsets = c.yOffsets; + valid = c.valid; + + return *this; +} + +bool Q3TextCursor::operator==(const Q3TextCursor &c) const +{ + return para == c.para && idx == c.idx; +} + +int Q3TextCursor::totalOffsetX() const +{ + int xoff = ox; + for (QStack<int>::ConstIterator xit = xOffsets.begin(); xit != xOffsets.end(); ++xit) + xoff += *xit; + return xoff; +} + +int Q3TextCursor::totalOffsetY() const +{ + int yoff = oy; + for (QStack<int>::ConstIterator yit = yOffsets.begin(); yit != yOffsets.end(); ++yit) + yoff += *yit; + return yoff; +} + +#ifndef QT_NO_TEXTCUSTOMITEM +void Q3TextCursor::gotoIntoNested(const QPoint &globalPos) +{ + if (!para) + return; + Q_ASSERT(para->at(idx)->isCustom()); + push(); + ox = 0; + int bl, y; + para->lineHeightOfChar(idx, &bl, &y); + oy = y + para->rect().y(); + ox = para->at(idx)->x; + Q3TextDocument* doc = document(); + para->at(idx)->customItem()->enterAt(this, doc, para, idx, ox, oy, globalPos-QPoint(ox,oy)); +} +#endif + +void Q3TextCursor::invalidateNested() +{ + if (nestedDepth()) { + QStack<Q3TextParagraph*>::Iterator it = paras.begin(); + QStack<int>::Iterator it2 = indices.begin(); + for (; it != paras.end(); ++it, ++it2) { + if (*it == para) + continue; + (*it)->invalidate(0); +#ifndef QT_NO_TEXTCUSTOMITEM + if ((*it)->at(*it2)->isCustom()) + (*it)->at(*it2)->customItem()->invalidate(); +#endif + } + } +} + +void Q3TextCursor::insert(const QString &str, bool checkNewLine, QVector<Q3TextStringChar> *formatting) +{ + tmpX = -1; + bool justInsert = true; + QString s(str); +#if defined(Q_WS_WIN) + if (checkNewLine) { + int i = 0; + while ((i = s.indexOf(QLatin1Char('\r'), i)) != -1) + s.remove(i ,1); + } +#endif + if (checkNewLine) + justInsert = s.indexOf(QLatin1Char('\n')) == -1; + if (justInsert) { // we ignore new lines and insert all in the current para at the current index + para->insert(idx, s.unicode(), s.length()); + if (formatting) { + for (int i = 0; i < (int)s.length(); ++i) { + if (formatting->at(i).format()) { + formatting->at(i).format()->addRef(); + para->string()->setFormat(idx + i, formatting->at(i).format(), true); + } + } + } + idx += s.length(); + } else { // we split at new lines + int start = -1; + int end; + int y = para->rect().y() + para->rect().height(); + int lastIndex = 0; + do { + end = s.indexOf(QLatin1Char('\n'), start + 1); // find line break + if (end == -1) // didn't find one, so end of line is end of string + end = s.length(); + int len = (start == -1 ? end : end - start - 1); + if (len > 0) // insert the line + para->insert(idx, s.unicode() + start + 1, len); + else + para->invalidate(0); + if (formatting) { // set formats to the chars of the line + for (int i = 0; i < len; ++i) { + if (formatting->at(i + lastIndex).format()) { + formatting->at(i + lastIndex).format()->addRef(); + para->string()->setFormat(i + idx, formatting->at(i + lastIndex).format(), true); + } + } + lastIndex += len; + } + start = end; // next start is at the end of this line + idx += len; // increase the index of the cursor to the end of the inserted text + if (s[end] == QLatin1Char('\n')) { // if at the end was a line break, break the line + splitAndInsertEmptyParagraph(false, true); + para->setEndState(-1); + para->prev()->format(-1, false); + lastIndex++; + } + + } while (end < (int)s.length()); + + para->format(-1, false); + int dy = para->rect().y() + para->rect().height() - y; + Q3TextParagraph *p = para; + p->setParagId(p->prev() ? p->prev()->paragId() + 1 : 0); + p = p->next(); + while (p) { + p->setParagId(p->prev()->paragId() + 1); + p->move(dy); + p->invalidate(0); + p->setEndState(-1); + p = p->next(); + } + } + + int h = para->rect().height(); + para->format(-1, true); + if (h != para->rect().height()) + invalidateNested(); + else if (para->document() && para->document()->parent()) + para->document()->nextDoubleBuffered = true; + + fixCursorPosition(); +} + +void Q3TextCursor::gotoLeft() +{ + if (para->string()->isRightToLeft()) + gotoNextLetter(); + else + gotoPreviousLetter(); +} + +void Q3TextCursor::gotoPreviousLetter() +{ + tmpX = -1; + + if (idx > 0) { + idx = para->string()->previousCursorPosition(idx); +#ifndef QT_NO_TEXTCUSTOMITEM + const Q3TextStringChar *tsc = para->at(idx); + if (tsc && tsc->isCustom() && tsc->customItem()->isNested()) + processNesting(EnterEnd); +#endif + } else if (para->prev()) { + para = para->prev(); + while (!para->isVisible() && para->prev()) + para = para->prev(); + idx = para->length() - 1; + } else if (nestedDepth()) { + pop(); + processNesting(Prev); + if (idx == -1) { + pop(); + if (idx > 0) { + idx = para->string()->previousCursorPosition(idx); +#ifndef QT_NO_TEXTCUSTOMITEM + const Q3TextStringChar *tsc = para->at(idx); + if (tsc && tsc->isCustom() && tsc->customItem()->isNested()) + processNesting(EnterEnd); +#endif + } else if (para->prev()) { + para = para->prev(); + idx = para->length() - 1; + } + } + } +} + +void Q3TextCursor::push() +{ + indices.push(idx); + paras.push(para); + xOffsets.push(ox); + yOffsets.push(oy); +} + +void Q3TextCursor::pop() +{ + if (indices.isEmpty()) + return; + idx = indices.pop(); + para = paras.pop(); + ox = xOffsets.pop(); + oy = yOffsets.pop(); +} + +void Q3TextCursor::restoreState() +{ + while (!indices.isEmpty()) + pop(); +} + +bool Q3TextCursor::place(const QPoint &p, Q3TextParagraph *s, bool link) +{ + QPoint pos(p); + QRect r; + Q3TextParagraph *str = s; + if (pos.y() < s->rect().y()) { + pos.setY(s->rect().y()); +#ifdef Q_WS_MAC + pos.setX(s->rect().x()); +#endif + } + while (s) { + r = s->rect(); + r.setWidth(document() ? document()->width() : QWIDGETSIZE_MAX); + if (s->isVisible()) + str = s; + if (pos.y() >= r.y() && pos.y() <= r.y() + r.height()) + break; + if (!s->next()) { +#ifdef Q_WS_MAC + pos.setX(s->rect().x() + s->rect().width()); +#endif + break; + } + s = s->next(); + } + + if (!s || !str) + return false; + + s = str; + + setParagraph(s); + int y = s->rect().y(); + int lines = s->lines(); + Q3TextStringChar *chr = 0; + int index = 0; + int i = 0; + int cy = 0; + int ch = 0; + for (; i < lines; ++i) { + chr = s->lineStartOfLine(i, &index); + cy = s->lineY(i); + ch = s->lineHeight(i); + if (!chr) + return false; + if (pos.y() <= y + cy + ch) + break; + } + int nextLine; + if (i < lines - 1) + s->lineStartOfLine(i+1, &nextLine); + else + nextLine = s->length(); + i = index; + int x = s->rect().x(); + if (pos.x() < x) + pos.setX(x + 1); + int cw; + int curpos = s->length()-1; + int dist = 10000000; + bool inCustom = false; + while (i < nextLine) { + chr = s->at(i); + int cpos = x + chr->x; + cw = s->string()->width(i); +#ifndef QT_NO_TEXTCUSTOMITEM + if (chr->isCustom() && chr->customItem()->isNested()) { + if (pos.x() >= cpos && pos.x() <= cpos + cw && + pos.y() >= y + cy && pos.y() <= y + cy + chr->height()) { + inCustom = true; + curpos = i; + break; + } + } else +#endif + { + if(chr->rightToLeft) + cpos += cw; + int diff = cpos - pos.x(); + bool dm = diff < 0 ? !chr->rightToLeft : chr->rightToLeft; + if ((QABS(diff) < dist || (dist == diff && dm == true)) && para->string()->validCursorPosition(i)) { + dist = QABS(diff); + if (!link || pos.x() >= x + chr->x) + curpos = i; + } + } + i++; + } + setIndex(curpos); + +#ifndef QT_NO_TEXTCUSTOMITEM + if (inCustom && para->document() && para->at(curpos)->isCustom() && para->at(curpos)->customItem()->isNested()) { + Q3TextDocument *oldDoc = para->document(); + gotoIntoNested(pos); + if (oldDoc == para->document()) + return true; + QPoint p(pos.x() - offsetX(), pos.y() - offsetY()); + if (!place(p, document()->firstParagraph(), link)) + pop(); + } +#endif + return true; +} + +bool Q3TextCursor::processNesting(Operation op) +{ + if (!para->document()) + return false; + Q3TextDocument* doc = para->document(); + push(); + ox = para->at(idx)->x; + int bl, y; + para->lineHeightOfChar(idx, &bl, &y); + oy = y + para->rect().y(); + bool ok = false; + +#ifndef QT_NO_TEXTCUSTOMITEM + switch (op) { + case EnterBegin: + ok = para->at(idx)->customItem()->enter(this, doc, para, idx, ox, oy); + break; + case EnterEnd: + ok = para->at(idx)->customItem()->enter(this, doc, para, idx, ox, oy, true); + break; + case Next: + ok = para->at(idx)->customItem()->next(this, doc, para, idx, ox, oy); + break; + case Prev: + ok = para->at(idx)->customItem()->prev(this, doc, para, idx, ox, oy); + break; + case Down: + ok = para->at(idx)->customItem()->down(this, doc, para, idx, ox, oy); + break; + case Up: + ok = para->at(idx)->customItem()->up(this, doc, para, idx, ox, oy); + break; + } + if (!ok) +#endif + pop(); + return ok; +} + +void Q3TextCursor::gotoRight() +{ + if (para->string()->isRightToLeft()) + gotoPreviousLetter(); + else + gotoNextLetter(); +} + +void Q3TextCursor::gotoNextLetter() +{ + tmpX = -1; + +#ifndef QT_NO_TEXTCUSTOMITEM + const Q3TextStringChar *tsc = para->at(idx); + if (tsc && tsc->isCustom() && tsc->customItem()->isNested()) { + if (processNesting(EnterBegin)) + return; + } +#endif + + if (idx < para->length() - 1) { + idx = para->string()->nextCursorPosition(idx); + } else if (para->next()) { + para = para->next(); + while (!para->isVisible() && para->next()) + para = para->next(); + idx = 0; + } else if (nestedDepth()) { + pop(); + processNesting(Next); + if (idx == -1) { + pop(); + if (idx < para->length() - 1) { + idx = para->string()->nextCursorPosition(idx); + } else if (para->next()) { + para = para->next(); + idx = 0; + } + } + } +} + +void Q3TextCursor::gotoUp() +{ + int indexOfLineStart; + int line; + Q3TextStringChar *c = para->lineStartOfChar(idx, &indexOfLineStart, &line); + if (!c) + return; + + if (tmpX < 0) + tmpX = x(); + + if (indexOfLineStart == 0) { + if (!para->prev()) { + if (!nestedDepth()) + return; + pop(); + processNesting(Up); + if (idx == -1) { + pop(); + if (!para->prev()) + return; + idx = tmpX = 0; + } else { + tmpX = -1; + return; + } + } + Q3TextParagraph *p = para->prev(); + while (p && !p->isVisible()) + p = p->prev(); + if (p) + para = p; + int lastLine = para->lines() - 1; + if (!para->lineStartOfLine(lastLine, &indexOfLineStart)) + return; + idx = indexOfLineStart; + while (idx < para->length()-1 && para->at(idx)->x < tmpX) + ++idx; + if (idx > indexOfLineStart && + para->at(idx)->x - tmpX > tmpX - para->at(idx-1)->x) + --idx; + } else { + --line; + int oldIndexOfLineStart = indexOfLineStart; + if (!para->lineStartOfLine(line, &indexOfLineStart)) + return; + idx = indexOfLineStart; + while (idx < oldIndexOfLineStart-1 && para->at(idx)->x < tmpX) + ++idx; + if (idx > indexOfLineStart && + para->at(idx)->x - tmpX > tmpX - para->at(idx-1)->x) + --idx; + } + fixCursorPosition(); +} + +void Q3TextCursor::gotoDown() +{ + int indexOfLineStart; + int line; + Q3TextStringChar *c = para->lineStartOfChar(idx, &indexOfLineStart, &line); + if (!c) + return; + + if (tmpX < 0) + tmpX = x(); + + if (line == para->lines() - 1) { + if (!para->next()) { + if (!nestedDepth()) + return; + pop(); + processNesting(Down); + if (idx == -1) { + pop(); + if (!para->next()) + return; + idx = tmpX = 0; + } else { + tmpX = -1; + return; + } + } + Q3TextParagraph *s = para->next(); + while (s && !s->isVisible()) + s = s->next(); + if (s) + para = s; + if (!para->lineStartOfLine(0, &indexOfLineStart)) + return; + int end; + if (para->lines() == 1) + end = para->length(); + else + para->lineStartOfLine(1, &end); + + idx = indexOfLineStart; + while (idx < end-1 && para->at(idx)->x < tmpX) + ++idx; + if (idx > indexOfLineStart && + para->at(idx)->x - tmpX > tmpX - para->at(idx-1)->x) + --idx; + } else { + ++line; + int end; + if (line == para->lines() - 1) + end = para->length(); + else + para->lineStartOfLine(line + 1, &end); + if (!para->lineStartOfLine(line, &indexOfLineStart)) + return; + idx = indexOfLineStart; + while (idx < end-1 && para->at(idx)->x < tmpX) + ++idx; + if (idx > indexOfLineStart && + para->at(idx)->x - tmpX > tmpX - para->at(idx-1)->x) + --idx; + } + fixCursorPosition(); +} + +void Q3TextCursor::gotoLineEnd() +{ + tmpX = -1; + int indexOfLineStart; + int line; + Q3TextStringChar *c = para->lineStartOfChar(idx, &indexOfLineStart, &line); + if (!c) + return; + + if (line == para->lines() - 1) { + idx = para->length() - 1; + } else { + c = para->lineStartOfLine(++line, &indexOfLineStart); + indexOfLineStart--; + idx = indexOfLineStart; + } +} + +void Q3TextCursor::gotoLineStart() +{ + tmpX = -1; + int indexOfLineStart; + int line; + Q3TextStringChar *c = para->lineStartOfChar(idx, &indexOfLineStart, &line); + if (!c) + return; + + idx = indexOfLineStart; +} + +void Q3TextCursor::gotoHome() +{ + if (topParagraph()->document()) + gotoPosition(topParagraph()->document()->firstParagraph()); + else + gotoLineStart(); +} + +void Q3TextCursor::gotoEnd() +{ + if (topParagraph()->document() && topParagraph()->document()->lastParagraph()->isValid()) + gotoPosition(topParagraph()->document()->lastParagraph(), + topParagraph()->document()->lastParagraph()->length() - 1); + else + gotoLineEnd(); +} + +void Q3TextCursor::gotoPageUp(int visibleHeight) +{ + int targetY = globalY() - visibleHeight; + Q3TextParagraph* old; int index; + do { + old = para; index = idx; + gotoUp(); + } while ((old != para || index != idx) && globalY() > targetY); +} + +void Q3TextCursor::gotoPageDown(int visibleHeight) +{ + int targetY = globalY() + visibleHeight; + Q3TextParagraph* old; int index; + do { + old = para; index = idx; + gotoDown(); + } while ((old != para || index != idx) && globalY() < targetY); +} + +void Q3TextCursor::gotoWordRight() +{ + if (para->string()->isRightToLeft()) + gotoPreviousWord(); + else + gotoNextWord(); +} + +void Q3TextCursor::gotoWordLeft() +{ + if (para->string()->isRightToLeft()) + gotoNextWord(); + else + gotoPreviousWord(); +} + +static bool is_seperator(const QChar &c, bool onlySpace) +{ + if (onlySpace) + return c.isSpace(); + return c.isSpace() || + c == QLatin1Char('\t') || + c == QLatin1Char('.') || + c == QLatin1Char(',') || + c == QLatin1Char(':') || + c == QLatin1Char(';') || + c == QLatin1Char('-') || + c == QLatin1Char('<') || + c == QLatin1Char('>') || + c == QLatin1Char('[') || + c == QLatin1Char(']') || + c == QLatin1Char('(') || + c == QLatin1Char(')') || + c == QLatin1Char('{') || + c == QLatin1Char('}'); +} + +void Q3TextCursor::gotoPreviousWord(bool onlySpace) +{ + gotoPreviousLetter(); + tmpX = -1; + Q3TextString *s = para->string(); + bool allowSame = false; + if (idx == ((int)s->length()-1)) + return; + for (int i = idx; i >= 0; --i) { + if (is_seperator(s->at(i).c, onlySpace)) { + if (!allowSame) + continue; + idx = i + 1; + return; + } + if (!allowSame && !is_seperator(s->at(i).c, onlySpace)) + allowSame = true; + } + idx = 0; +} + +void Q3TextCursor::gotoNextWord(bool onlySpace) +{ + tmpX = -1; + Q3TextString *s = para->string(); + bool allowSame = false; + for (int i = idx; i < (int)s->length(); ++i) { + if (!is_seperator(s->at(i).c, onlySpace)) { + if (!allowSame) + continue; + idx = i; + return; + } + if (!allowSame && is_seperator(s->at(i).c, onlySpace)) + allowSame = true; + + } + + if (idx < ((int)s->length()-1)) { + gotoLineEnd(); + } else if (para->next()) { + Q3TextParagraph *p = para->next(); + while (p && !p->isVisible()) + p = p->next(); + if (s) { + para = p; + idx = 0; + } + } else { + gotoLineEnd(); + } +} + +bool Q3TextCursor::atParagStart() +{ + return idx == 0; +} + +bool Q3TextCursor::atParagEnd() +{ + return idx == para->length() - 1; +} + +void Q3TextCursor::splitAndInsertEmptyParagraph(bool ind, bool updateIds) +{ + if (!para->document()) + return; + tmpX = -1; + Q3TextFormat *f = 0; + if (para->document()->useFormatCollection()) { + f = para->at(idx)->format(); + if (idx == para->length() - 1 && idx > 0) + f = para->at(idx - 1)->format(); + if (f->isMisspelled()) { + f->removeRef(); + f = para->document()->formatCollection()->format(f->font(), f->color()); + } + } + + if (atParagEnd()) { + Q3TextParagraph *n = para->next(); + Q3TextParagraph *s = para->document()->createParagraph(para->document(), para, n, updateIds); + if (f) + s->setFormat(0, 1, f, true); + s->copyParagData(para); + if (ind) { + int oi, ni; + s->indent(&oi, &ni); + para = s; + idx = ni; + } else { + para = s; + idx = 0; + } + } else if (atParagStart()) { + Q3TextParagraph *p = para->prev(); + Q3TextParagraph *s = para->document()->createParagraph(para->document(), p, para, updateIds); + if (f) + s->setFormat(0, 1, f, true); + s->copyParagData(para); + if (ind) { + s->indent(); + s->format(); + indent(); + para->format(); + } + } else { + QString str = para->string()->toString().mid(idx, 0xFFFFFF); + Q3TextParagraph *n = para->next(); + Q3TextParagraph *s = para->document()->createParagraph(para->document(), para, n, updateIds); + s->copyParagData(para); + s->remove(0, 1); + s->append(str, true); + for (int i = 0; i < str.length(); ++i) { + Q3TextStringChar* tsc = para->at(idx + i); + s->setFormat(i, 1, tsc->format(), true); +#ifndef QT_NO_TEXTCUSTOMITEM + if (tsc->isCustom()) { + Q3TextCustomItem * item = tsc->customItem(); + s->at(i)->setCustomItem(item); + tsc->loseCustomItem(); + } +#endif + if (tsc->isAnchor()) + s->at(i)->setAnchor(tsc->anchorName(), + tsc->anchorHref()); + } + para->truncate(idx); + if (ind) { + int oi, ni; + s->indent(&oi, &ni); + para = s; + idx = ni; + } else { + para = s; + idx = 0; + } + } + + invalidateNested(); +} + +bool Q3TextCursor::remove() +{ + tmpX = -1; + if (!atParagEnd()) { + int next = para->string()->nextCursorPosition(idx); + para->remove(idx, next-idx); + int h = para->rect().height(); + para->format(-1, true); + if (h != para->rect().height()) + invalidateNested(); + else if (para->document() && para->document()->parent()) + para->document()->nextDoubleBuffered = true; + return false; + } else if (para->next()) { + para->join(para->next()); + invalidateNested(); + return true; + } + return false; +} + +/* needed to implement backspace the correct way */ +bool Q3TextCursor::removePreviousChar() +{ + tmpX = -1; + if (!atParagStart()) { + para->remove(idx-1, 1); + int h = para->rect().height(); + idx--; + // shouldn't be needed, just to make sure. + fixCursorPosition(); + para->format(-1, true); + if (h != para->rect().height()) + invalidateNested(); + else if (para->document() && para->document()->parent()) + para->document()->nextDoubleBuffered = true; + return false; + } else if (para->prev()) { + para = para->prev(); + para->join(para->next()); + invalidateNested(); + return true; + } + return false; +} + +void Q3TextCursor::indent() +{ + int oi = 0, ni = 0; + para->indent(&oi, &ni); + if (oi == ni) + return; + + if (idx >= oi) + idx += ni - oi; + else + idx = ni; +} + +void Q3TextCursor::fixCursorPosition() +{ + // searches for the closest valid cursor position + if (para->string()->validCursorPosition(idx)) + return; + + int lineIdx; + Q3TextStringChar *start = para->lineStartOfChar(idx, &lineIdx, 0); + int x = para->string()->at(idx).x; + int diff = QABS(start->x - x); + int best = lineIdx; + + Q3TextStringChar *c = start; + ++c; + + Q3TextStringChar *end = ¶->string()->at(para->length()-1); + while (c <= end && !c->lineStart) { + int xp = c->x; + if (c->rightToLeft) + xp += para->string()->width(lineIdx + (c-start)); + int ndiff = QABS(xp - x); + if (ndiff < diff && para->string()->validCursorPosition(lineIdx + (c-start))) { + diff = ndiff; + best = lineIdx + (c-start); + } + ++c; + } + idx = best; +} + + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Q3TextDocument::Q3TextDocument(Q3TextDocument *p) + : par(p), parentPar(0) +#ifndef QT_NO_TEXTCUSTOMITEM + , tc(0) +#endif + , tArray(0), tStopWidth(0) +{ + fCollection = par ? par->fCollection : new Q3TextFormatCollection; + init(); +} + +void Q3TextDocument::init() +{ + oTextValid = true; + mightHaveCustomItems = false; + if (par) + par->insertChild(this); + pProcessor = 0; + useFC = true; + pFormatter = 0; + indenter = 0; + fParag = 0; + txtFormat = Qt::AutoText; + preferRichText = false; + pages = false; + focusIndicator.parag = 0; + minw = 0; + wused = 0; + minwParag = curParag = 0; + align = Qt::AlignAuto; + nSelections = 1; + + setStyleSheet(Q3StyleSheet::defaultSheet()); +#ifndef QT_NO_MIME + factory_ = Q3MimeSourceFactory::defaultFactory(); +#endif + contxt.clear(); + + underlLinks = par ? par->underlLinks : true; + backBrush = 0; + buf_pixmap = 0; + nextDoubleBuffered = false; + + if (par) + withoutDoubleBuffer = par->withoutDoubleBuffer; + else + withoutDoubleBuffer = false; + + lParag = fParag = createParagraph(this, 0, 0); + + cx = 0; + cy = 2; + if (par) + cx = cy = 0; + cw = 600; + vw = 0; + flow_ = new Q3TextFlow; + flow_->setWidth(cw); + + leftmargin = rightmargin = 4; + scaleFontsFactor = 1; + + commandHistory = new Q3TextCommandHistory(100); + tStopWidth = formatCollection()->defaultFormat()->width(QLatin1Char('x')) * 8; +} + +Q3TextDocument::~Q3TextDocument() +{ + delete commandHistory; + if (par) + par->removeChild(this); + clear(); + delete flow_; + if (!par) { + delete pFormatter; + delete fCollection; + } + delete pProcessor; + delete buf_pixmap; + delete indenter; + delete backBrush; + delete [] tArray; +} + +void Q3TextDocument::clear(bool createEmptyParag) +{ + while (fParag) { + Q3TextParagraph *p = fParag->next(); + delete fParag; + fParag = p; + } + if (flow_) + flow_->clear(); + fParag = lParag = 0; + if (createEmptyParag) + fParag = lParag = createParagraph(this); + selections.clear(); + oText.clear(); + oTextValid = false; +} + +int Q3TextDocument::widthUsed() const +{ + return wused + 2*border_tolerance; +} + +int Q3TextDocument::height() const +{ + int h = 0; + if (lParag) + h = lParag->rect().top() + lParag->rect().height() + 1; + int fh = flow_->boundingRect().bottom(); + return qMax(h, fh); +} + + + +Q3TextParagraph *Q3TextDocument::createParagraph(Q3TextDocument *dc, Q3TextParagraph *pr, Q3TextParagraph *nx, bool updateIds) +{ + return new Q3TextParagraph(dc, pr, nx, updateIds); +} + +bool Q3TextDocument::setMinimumWidth(int needed, int used, Q3TextParagraph *p) +{ + if (needed == -1) { + minw = 0; + wused = 0; + p = 0; + } + if (p == minwParag) { + if (minw > needed) { + Q3TextParagraph *tp = fParag; + while (tp) { + if (tp != p && tp->minwidth > needed) { + needed = tp->minwidth; + minwParag = tp; + } + tp = tp->n; + } + } + minw = needed; + emit minimumWidthChanged(minw); + } else if (needed > minw) { + minw = needed; + minwParag = p; + emit minimumWidthChanged(minw); + } + wused = qMax(wused, used); + wused = qMax(wused, minw); + cw = qMax(minw, cw); + return true; +} + +void Q3TextDocument::setPlainText(const QString &text) +{ + preferRichText = false; + clear(); + oTextValid = true; + oText = text; + + int lastNl = 0; + int nl = text.indexOf(QLatin1Char('\n')); + if (nl == -1) { + lParag = createParagraph(this, lParag, 0); + if (!fParag) + fParag = lParag; + QString s = text; + if (!s.isEmpty()) { + if (s[(int)s.length() - 1] == QLatin1Char('\r')) + s.remove(s.length() - 1, 1); + lParag->append(s); + } + } else { + for (;;) { + lParag = createParagraph(this, lParag, 0); + if (!fParag) + fParag = lParag; + int l = nl - lastNl; + if (l > 0) { + if (text.unicode()[nl-1] == QLatin1Char('\r')) + l--; + QString cs = QString::fromRawData(text.unicode()+lastNl, l); + lParag->append(cs); + } + if (nl == (int)text.length()) + break; + lastNl = nl + 1; + nl = text.indexOf(QLatin1Char('\n'), nl + 1); + if (nl == -1) + nl = text.length(); + } + } + if (!lParag) + lParag = fParag = createParagraph(this, 0, 0); +} + +struct Q3TextDocumentTag { + Q3TextDocumentTag(){} + Q3TextDocumentTag(const QString&n, const Q3StyleSheetItem* s, const Q3TextFormat& f) + :name(n),style(s), format(f), alignment(Qt::AlignAuto), direction(QChar::DirON),liststyle(Q3StyleSheetItem::ListDisc) { + wsm = Q3StyleSheetItem::WhiteSpaceNormal; + } + QString name; + const Q3StyleSheetItem* style; + QString anchorHref; + Q3StyleSheetItem::WhiteSpaceMode wsm; + Q3TextFormat format; + signed int alignment : 16; + signed int direction : 5; + Q3StyleSheetItem::ListStyle liststyle; + + Q3TextDocumentTag( const Q3TextDocumentTag& t) { + name = t.name; + style = t.style; + anchorHref = t.anchorHref; + wsm = t.wsm; + format = t.format; + alignment = t.alignment; + direction = t.direction; + liststyle = t.liststyle; + } + Q3TextDocumentTag& operator=(const Q3TextDocumentTag& t) { + name = t.name; + style = t.style; + anchorHref = t.anchorHref; + wsm = t.wsm; + format = t.format; + alignment = t.alignment; + direction = t.direction; + liststyle = t.liststyle; + return *this; + } + + Q_DUMMY_COMPARISON_OPERATOR(Q3TextDocumentTag) +}; + + +#define NEWPAR \ + do{ \ + if (!hasNewPar) { \ + if (!textEditMode && curpar && curpar->length()>1 \ + && curpar->at(curpar->length()-2)->c == QChar::LineSeparator) \ + curpar->remove(curpar->length()-2, 1); \ + curpar = createParagraph(this, curpar, curpar->next()); \ + styles.append(vec); \ + vec = 0; \ + } \ + hasNewPar = true; \ + curpar->rtext = true; \ + curpar->align = curtag.alignment; \ + curpar->lstyle = curtag.liststyle; \ + curpar->litem = (curtag.style->displayMode() == Q3StyleSheetItem::DisplayListItem); \ + curpar->str->setDirection((QChar::Direction)curtag.direction); \ + space = true; \ + tabExpansionColumn = 0; \ + delete vec; \ + vec = new QVector<Q3StyleSheetItem *>(); \ + for (QStack<Q3TextDocumentTag>::Iterator it = tags.begin(); it != tags.end(); ++it) \ + vec->append(const_cast<Q3StyleSheetItem *>((*it).style)); \ + vec->append(const_cast<Q3StyleSheetItem *>(curtag.style)); \ + } while(false); + + +void Q3TextDocument::setRichText(const QString &text, const QString &context, const Q3TextFormat *initialFormat) +{ + preferRichText = true; + if (!context.isEmpty()) + setContext(context); + clear(); + fParag = lParag = createParagraph(this); + oTextValid = true; + oText = text; + setRichTextInternal(text, 0, initialFormat); + fParag->rtext = true; +} + +void Q3TextDocument::setRichTextInternal(const QString &text, Q3TextCursor* cursor, const Q3TextFormat *initialFormat) +{ + Q3TextParagraph* curpar = lParag; + int pos = 0; + QStack<Q3TextDocumentTag> tags; + if (!initialFormat) + initialFormat = formatCollection()->defaultFormat(); + Q3TextDocumentTag initag(QLatin1String(""), sheet_->item(QLatin1String("")), *initialFormat); + if (bodyText.isValid()) + initag.format.setColor(bodyText); + Q3TextDocumentTag curtag = initag; + bool space = true; + bool canMergeLi = false; + + bool textEditMode = false; + int tabExpansionColumn = 0; + + const QChar* doc = text.unicode(); + int length = text.length(); + bool hasNewPar = curpar->length() <= 1; + QString anchorName; + + // style sheet handling for margin and line spacing calculation below + Q3TextParagraph* stylesPar = curpar; + QVector<Q3StyleSheetItem *>* vec = 0; + QList< QVector<Q3StyleSheetItem *> *> styles; + + if (cursor) { + cursor->splitAndInsertEmptyParagraph(); + Q3TextCursor tmp = *cursor; + tmp.gotoPreviousLetter(); + stylesPar = curpar = tmp.paragraph(); + hasNewPar = true; + textEditMode = true; + } else { + NEWPAR; + } + + // set rtext spacing to false for the initial paragraph. + curpar->rtext = false; + + QString wellKnownTags = QLatin1String("br hr wsp table qt body meta title"); + + while (pos < length) { + if (hasPrefix(doc, length, pos, QLatin1Char('<'))){ + if (!hasPrefix(doc, length, pos+1, QLatin1Char('/'))) { + // open tag + QMap<QString, QString> attr; + QMap<QString, QString>::Iterator it, end = attr.end(); + bool emptyTag = false; + QString tagname = parseOpenTag(doc, length, pos, attr, emptyTag); + if (tagname.isEmpty()) + continue; // nothing we could do with this, probably parse error + + const Q3StyleSheetItem* nstyle = sheet_->item(tagname); + + if (nstyle) { + // we might have to close some 'forgotten' tags + while (!nstyle->allowedInContext(curtag.style)) { + QString msg; + msg.sprintf("QText Warning: Document not valid ('%s' not allowed in '%s' #%d)", + tagname.ascii(), curtag.style->name().ascii(), pos); + sheet_->error(msg); + if (tags.isEmpty()) + break; + curtag = tags.pop(); + } + + /* special handling for p and li for HTML + compatibility. We do not want to embed blocks in + p, and we do not want new blocks inside non-empty + lis. Plus we want to merge empty lis sometimes. */ + if(nstyle->displayMode() == Q3StyleSheetItem::DisplayListItem) { + canMergeLi = true; + } else if (nstyle->displayMode() == Q3StyleSheetItem::DisplayBlock) { + while (curtag.style->name() == QLatin1String("p")) { + if (tags.isEmpty()) + break; + curtag = tags.pop(); + } + + if (curtag.style->displayMode() == Q3StyleSheetItem::DisplayListItem) { + // we are in a li and a new block comes along + if (nstyle->name() == QString(QLatin1String("ul")) || nstyle->name() == QLatin1String("ol")) + hasNewPar = false; // we want an empty li (like most browsers) + if (!hasNewPar) { + /* do not add new blocks inside + non-empty lis */ + while (curtag.style->displayMode() == Q3StyleSheetItem::DisplayListItem) { + if (tags.isEmpty()) + break; + curtag = tags.pop(); + } + } else if (canMergeLi) { + /* we have an empty li and a block + comes along, merge them */ + nstyle = curtag.style; + } + canMergeLi = false; + } + } + } + +#ifndef QT_NO_TEXTCUSTOMITEM + Q3TextCustomItem* custom = 0; +#else + bool custom = false; +#endif + + // some well-known tags, some have a nstyle, some not + if (wellKnownTags.contains(tagname)) { + if (tagname == QLatin1String("br")) { + emptyTag = space = true; + int index = qMax(curpar->length(),1) - 1; + Q3TextFormat format = curtag.format.makeTextFormat(nstyle, attr, scaleFontsFactor); + curpar->append(QString(QChar(QChar::LineSeparator))); + curpar->setFormat(index, 1, &format); + hasNewPar = false; + } else if (tagname == QLatin1String("hr")) { + emptyTag = space = true; +#ifndef QT_NO_TEXTCUSTOMITEM + custom = tag(sheet_, tagname, attr, contxt, *factory_ , emptyTag, this); +#endif + } else if (tagname == QLatin1String("table")) { + emptyTag = space = true; +#ifndef QT_NO_TEXTCUSTOMITEM + Q3TextFormat format = curtag.format.makeTextFormat( nstyle, attr, scaleFontsFactor); + curpar->setAlignment(curtag.alignment); + custom = parseTable(attr, format, doc, length, pos, curpar); +#endif + } else if (tagname == QLatin1String("qt") || tagname == QLatin1String("body")) { + it = attr.find(QLatin1String("bgcolor")); + if (it != end) { + QBrush *b = new QBrush(QColor(*it)); + setPaper(b); + } + it = attr.find(QLatin1String("background")); + if (it != end) { +#ifndef QT_NO_MIME + QImage img; + QString bg = *it; + const QMimeSource* m = factory_->data(bg, contxt); + if (!m) { + qCritical("QRichText: no mimesource for %s", + QFile::encodeName(bg).data()); + } else { + if (!Q3ImageDrag::decode(m, img)) { + qCritical("Q3TextImage: cannot decode %s", + QFile::encodeName(bg).data()); + } + } + if (!img.isNull()) { + QBrush *b = new QBrush(QColor(), QPixmap(img)); + setPaper(b); + } +#endif + } + it = attr.find(QLatin1String("text")); + if (it != end) { + QColor c(*it); + initag.format.setColor(c); + curtag.format.setColor(c); + bodyText = c; + } + it = attr.find(QLatin1String("link")); + if (it != end) + linkColor = QColor(*it); + it = attr.find(QLatin1String("title")); + if (it != end) + attribs.insert(QLatin1String("title"), *it); + + if (textEditMode) { + it = attr.find(QLatin1String("style")); + if (it != end) { + QString a = *it; + int count = a.count(QLatin1Char(';')) + 1; + for (int s = 0; s < count; s++) { + QString style = a.section(QLatin1Char(';'), s, s); + if (style.startsWith(QLatin1String("font-size:")) && style.endsWith(QLatin1String("pt"))) { + scaleFontsFactor = double(formatCollection()->defaultFormat()->fn.pointSize()) / + style.mid(10, style.length() - 12).toInt(); + } + } + } + nstyle = 0; // ignore body in textEditMode + } + // end qt- and body-tag handling + } else if (tagname == QLatin1String("meta")) { + if (attr[QLatin1String("name")] == QLatin1String("qrichtext") && attr[QLatin1String("content")] == QLatin1String("1")) + textEditMode = true; + } else if (tagname == QLatin1String("title")) { + QString title; + while (pos < length) { + if (hasPrefix(doc, length, pos, QLatin1Char('<')) && hasPrefix(doc, length, pos+1, QLatin1Char('/')) && + parseCloseTag(doc, length, pos) == QLatin1String("title")) + break; + title += doc[pos]; + ++pos; + } + attribs.insert(QLatin1String("title"), title); + } + } // end of well-known tag handling + +#ifndef QT_NO_TEXTCUSTOMITEM + if (!custom) // try generic custom item + custom = tag(sheet_, tagname, attr, contxt, *factory_ , emptyTag, this); +#endif + if (!nstyle && !custom) // we have no clue what this tag could be, ignore it + continue; + + if (custom) { +#ifndef QT_NO_TEXTCUSTOMITEM + int index = qMax(curpar->length(),1) - 1; + Q3TextFormat format = curtag.format.makeTextFormat(nstyle, attr, scaleFontsFactor); + curpar->append(QString(QLatin1Char('*'))); + Q3TextFormat* f = formatCollection()->format(&format); + curpar->setFormat(index, 1, f); + curpar->at(index)->setCustomItem(custom); + if (!curtag.anchorHref.isEmpty()) + curpar->at(index)->setAnchor(QString(), curtag.anchorHref); + if (!anchorName.isEmpty() ) { + curpar->at(index)->setAnchor(anchorName, curpar->at(index)->anchorHref()); + anchorName.clear(); + } + registerCustomItem(custom, curpar); + hasNewPar = false; +#endif + } else if (!emptyTag) { + /* if we do nesting, push curtag on the stack, + otherwise reinint curag. */ + if (curtag.style->name() != tagname || nstyle->selfNesting()) { + tags.push(curtag); + } else { + if (!tags.isEmpty()) + curtag = tags.top(); + else + curtag = initag; + } + + curtag.name = tagname; + curtag.style = nstyle; + curtag.name = tagname; + curtag.style = nstyle; + if (nstyle->whiteSpaceMode() != Q3StyleSheetItem::WhiteSpaceModeUndefined) + curtag.wsm = nstyle->whiteSpaceMode(); + + /* netscape compatibility: eat a newline and only a newline if a pre block starts */ + if (curtag.wsm == Q3StyleSheetItem::WhiteSpacePre && + nstyle->displayMode() == Q3StyleSheetItem::DisplayBlock) + eat(doc, length, pos, QLatin1Char('\n')); + + /* ignore whitespace for inline elements if there + was already one*/ + if (!textEditMode && + (curtag.wsm == Q3StyleSheetItem::WhiteSpaceNormal + || curtag.wsm == Q3StyleSheetItem::WhiteSpaceNoWrap) + && (space || nstyle->displayMode() != Q3StyleSheetItem::DisplayInline)) + eatSpace(doc, length, pos); + + curtag.format = curtag.format.makeTextFormat(nstyle, attr, scaleFontsFactor); + if (nstyle->isAnchor()) { + if (!anchorName.isEmpty()) + anchorName += QLatin1String("#") + attr[QLatin1String("name")]; + else + anchorName = attr[QLatin1String("name")]; + curtag.anchorHref = attr[QLatin1String("href")]; + } + + if (nstyle->alignment() != Q3StyleSheetItem::Undefined) + curtag.alignment = nstyle->alignment(); + + if (nstyle->listStyle() != Q3StyleSheetItem::ListStyleUndefined) + curtag.liststyle = nstyle->listStyle(); + + if (nstyle->displayMode() == Q3StyleSheetItem::DisplayBlock + || nstyle->displayMode() == Q3StyleSheetItem::DisplayListItem) { + + if (nstyle->name() == QLatin1String("ol") || + nstyle->name() == QLatin1String("ul") || + nstyle->name() == QLatin1String("li")) { + QString type = attr[QLatin1String("type")]; + if (!type.isEmpty()) { + if (type == QLatin1String("1")) { + curtag.liststyle = Q3StyleSheetItem::ListDecimal; + } else if (type == QLatin1String("a")) { + curtag.liststyle = Q3StyleSheetItem::ListLowerAlpha; + } else if (type == QLatin1String("A")) { + curtag.liststyle = Q3StyleSheetItem::ListUpperAlpha; + } else { + type = type.toLower(); + if (type == QLatin1String("square")) + curtag.liststyle = Q3StyleSheetItem::ListSquare; + else if (type == QLatin1String("disc")) + curtag.liststyle = Q3StyleSheetItem::ListDisc; + else if (type == QLatin1String("circle")) + curtag.liststyle = Q3StyleSheetItem::ListCircle; + } + } + } + + + /* Internally we treat ordered and bullet + lists the same for margin calculations. In + order to have fast pointer compares in the + xMargin() functions we restrict ourselves to + <ol>. Once we calculate the margins in the + parser rathern than later, the unelegance of + this approach goes awy + */ + if (nstyle->name() == QLatin1String("ul")) + curtag.style = sheet_->item(QLatin1String("ol")); + + it = attr.find(QLatin1String("align")); + if (it != end) { + QString align = (*it).toLower(); + if (align == QLatin1String("center")) + curtag.alignment = Qt::AlignCenter; + else if (align == QLatin1String("right")) + curtag.alignment = Qt::AlignRight; + else if (align == QLatin1String("justify")) + curtag.alignment = Qt::AlignJustify; + } + it = attr.find(QLatin1String("dir")); + if (it != end) { + QString dir = (*it).toLower(); + if (dir == QLatin1String("rtl")) + curtag.direction = QChar::DirR; + else if (dir == QLatin1String("ltr")) + curtag.direction = QChar::DirL; + } + + NEWPAR; + + if (curtag.style && curtag.style->displayMode() == Q3StyleSheetItem::DisplayListItem) { + it = attr.find(QLatin1String("value")); + if (it != end) + curpar->setListValue((*it).toInt()); + } + + it = attr.find(QLatin1String("style")); + if (it != end) { + QString a = *it; + bool ok = true; + int count = a.count(QLatin1Char(';'))+1; + for (int s = 0; ok && s < count; s++) { + QString style = a.section(QLatin1Char(';'), s, s); + if (style.startsWith(QLatin1String("margin-top:")) && style.endsWith(QLatin1String("px"))) + curpar->utm = 1+style.mid(11, style.length() - 13).toInt(&ok); + else if (style.startsWith(QLatin1String("margin-bottom:")) && style.endsWith(QLatin1String("px"))) + curpar->ubm = 1+style.mid(14, style.length() - 16).toInt(&ok); + else if (style.startsWith(QLatin1String("margin-left:")) && style.endsWith(QLatin1String("px"))) + curpar->ulm = 1+style.mid(12, style.length() - 14).toInt(&ok); + else if (style.startsWith(QLatin1String("margin-right:")) && style.endsWith(QLatin1String("px"))) + curpar->urm = 1+style.mid(13, style.length() - 15).toInt(&ok); + else if (style.startsWith(QLatin1String("text-indent:")) && style.endsWith(QLatin1String("px"))) + curpar->uflm = 1+style.mid(12, style.length() - 14).toInt(&ok); + } + if (!ok) // be pressmistic + curpar->utm = curpar->ubm = curpar->urm = curpar->ulm = 0; + } + } else if (nstyle->name() == QLatin1String("html")) { + it = attr.find(QLatin1String("dir")); + if (it != end) { + QString dir = (*it).toLower(); + if (dir == QLatin1String("rtl")) + curtag.direction = QChar::DirR; + else if (dir == QLatin1String("ltr")) + curtag.direction = QChar::DirL; + } + } + } + } else { + QString tagname = parseCloseTag(doc, length, pos); + if (tagname.isEmpty()) + continue; // nothing we could do with this, probably parse error + if (!sheet_->item(tagname)) // ignore unknown tags + continue; + if (tagname == QLatin1String("li")) + continue; + + // we close a block item. Since the text may continue, we need to have a new paragraph + bool needNewPar = curtag.style->displayMode() == Q3StyleSheetItem::DisplayBlock + || curtag.style->displayMode() == Q3StyleSheetItem::DisplayListItem; + + + // html slopiness: handle unbalanched tag closing + while (curtag.name != tagname) { + QString msg; + msg.sprintf("QText Warning: Document not valid ('%s' not closed before '%s' #%d)", + curtag.name.ascii(), tagname.ascii(), pos); + sheet_->error(msg); + if (tags.isEmpty()) + break; + curtag = tags.pop(); + } + + + // close the tag + if (!tags.isEmpty()) + curtag = tags.pop(); + else + curtag = initag; + + if (needNewPar) { + if (textEditMode && (tagname == QLatin1String("p") || tagname == QLatin1String("div"))) // preserve empty paragraphs + hasNewPar = false; + NEWPAR; + } + } + } else { + // normal contents + QString s; + QChar c; + while (pos < length && !hasPrefix(doc, length, pos, QLatin1Char('<'))){ + if (textEditMode) { + // text edit mode: we handle all white space but ignore newlines + c = parseChar(doc, length, pos, Q3StyleSheetItem::WhiteSpacePre); + if (c == QChar::LineSeparator) + break; + } else { + int l = pos; + c = parseChar(doc, length, pos, curtag.wsm); + + // in white space pre mode: treat any space as non breakable + // and expand tabs to eight character wide columns. + if (curtag.wsm == Q3StyleSheetItem::WhiteSpacePre) { + if (c == QLatin1Char('\t')) { + c = QLatin1Char(' '); + while((++tabExpansionColumn)%8) + s += c; + } + if (c == QChar::LineSeparator) + tabExpansionColumn = 0; + else + tabExpansionColumn++; + + } + if (c == QLatin1Char(' ') || c == QChar::LineSeparator) { + /* avoid overlong paragraphs by forcing a new + paragraph after 4096 characters. This case can + occur when loading undiscovered plain text + documents in rich text mode. Instead of hanging + forever, we do the trick. + */ + if (curtag.wsm == Q3StyleSheetItem::WhiteSpaceNormal && s.length() > 4096) do { + if (doc[l] == QLatin1Char('\n')) { + hasNewPar = false; // for a new paragraph ... + NEWPAR; + hasNewPar = false; // ... and make it non-reusable + c = QLatin1Char('\n'); // make sure we break below + break; + } + } while (++l < pos); + } + } + + if (c == QLatin1Char('\n')) + break; // break on newlines, pre delievers a QChar::LineSeparator + + bool c_isSpace = c.isSpace() && c.unicode() != 0x00a0U && !textEditMode; + + if (curtag.wsm == Q3StyleSheetItem::WhiteSpaceNormal && c_isSpace && space) + continue; + if (c == QLatin1Char('\r')) + continue; + space = c_isSpace; + s += c; + } + if (!s.isEmpty() && curtag.style->displayMode() != Q3StyleSheetItem::DisplayNone) { + hasNewPar = false; + int index = qMax(curpar->length(),1) - 1; + curpar->append(s); + if (curtag.wsm != Q3StyleSheetItem::WhiteSpaceNormal) { + Q3TextString *str = curpar->string(); + for (int i = index; i < index + s.length(); ++i) + str->at(i).nobreak = true; + } + + Q3TextFormat* f = formatCollection()->format(&curtag.format); + curpar->setFormat(index, s.length(), f, false); // do not use collection because we have done that already + f->ref += s.length() -1; // that what friends are for... + if (!curtag.anchorHref.isEmpty()) { + for (int i = 0; i < int(s.length()); i++) + curpar->at(index + i)->setAnchor(QString(), curtag.anchorHref); + } + if (!anchorName.isEmpty() ) { + for (int i = 0; i < int(s.length()); i++) + curpar->at(index + i)->setAnchor(anchorName, curpar->at(index + i)->anchorHref()); + anchorName.clear(); + } + } + } + } + + if (hasNewPar && curpar != fParag && !cursor && stylesPar != curpar) { + // cleanup unused last paragraphs + curpar = curpar->p; + delete curpar->n; + } + + if (!anchorName.isEmpty() ) { + curpar->at(curpar->length() - 1)->setAnchor(anchorName, curpar->at(curpar->length() - 1)->anchorHref()); + anchorName.clear(); + } + + setRichTextMarginsInternal(styles, stylesPar); + + if (cursor) { + cursor->gotoPreviousLetter(); + cursor->remove(); + } + while (!styles.isEmpty()) + delete styles.takeFirst(); + delete vec; +} + +void Q3TextDocument::setRichTextMarginsInternal(QList< QVector<Q3StyleSheetItem *> *>& styles, Q3TextParagraph* stylesPar) +{ + // margin and line spacing calculation + // qDebug("setRichTextMarginsInternal: styles.size() = %d", styles.size()); + QVector<Q3StyleSheetItem *>* prevStyle = 0; + int stylesIndex = 0; + QVector<Q3StyleSheetItem *>* curStyle = styles.size() ? styles.first() : 0; + QVector<Q3StyleSheetItem *>* nextStyle = + (++stylesIndex) < styles.size() ? styles.at(stylesIndex) : 0; + while (stylesPar) { + if (!curStyle) { + stylesPar = stylesPar->next(); + prevStyle = curStyle; + curStyle = nextStyle; + nextStyle = (++stylesIndex) < styles.size() ? styles.at(stylesIndex) : 0; + continue; + } + + int i, mar; + Q3StyleSheetItem* mainStyle = curStyle->size() ? (*curStyle)[curStyle->size()-1] : 0; + if (mainStyle && mainStyle->displayMode() == Q3StyleSheetItem::DisplayListItem) + stylesPar->setListItem(true); + int numLists = 0; + for (i = 0; i < (int)curStyle->size(); ++i) { + if ((*curStyle)[i]->displayMode() == Q3StyleSheetItem::DisplayBlock + && (*curStyle)[i]->listStyle() != Q3StyleSheetItem::ListStyleUndefined) + numLists++; + } + stylesPar->ldepth = numLists; + if (stylesPar->next() && nextStyle) { + // also set the depth of the next paragraph, required for the margin calculation + numLists = 0; + for (i = 0; i < (int)nextStyle->size(); ++i) { + if ((*nextStyle)[i]->displayMode() == Q3StyleSheetItem::DisplayBlock + && (*nextStyle)[i]->listStyle() != Q3StyleSheetItem::ListStyleUndefined) + numLists++; + } + stylesPar->next()->ldepth = numLists; + } + + // do the top margin + Q3StyleSheetItem* item = mainStyle; + int m; + if (stylesPar->utm > 0) { + m = stylesPar->utm-1; + stylesPar->utm = 0; + } else { + m = qMax(0, item->margin(Q3StyleSheetItem::MarginTop)); + if (stylesPar->ldepth) { + if (item->displayMode() == Q3StyleSheetItem::DisplayListItem) + m /= stylesPar->ldepth * stylesPar->ldepth; + else + m = 0; + } + } + for (i = (int)curStyle->size() - 2 ; i >= 0; --i) { + item = (*curStyle)[i]; + if (prevStyle && i < (int) prevStyle->size() && + ( item->displayMode() == Q3StyleSheetItem::DisplayBlock && + (*prevStyle)[i] == item)) + break; + // emulate CSS2' standard 0 vertical margin for multiple ul or ol tags + if (item->listStyle() != Q3StyleSheetItem::ListStyleUndefined && + (( i> 0 && (*curStyle)[i-1] == item) || (*curStyle)[i+1] == item)) + continue; + mar = qMax(0, item->margin(Q3StyleSheetItem::MarginTop)); + m = qMax(m, mar); + } + stylesPar->utm = m - stylesPar->topMargin(); + + // do the bottom margin + item = mainStyle; + if (stylesPar->ubm > 0) { + m = stylesPar->ubm-1; + stylesPar->ubm = 0; + } else { + m = qMax(0, item->margin(Q3StyleSheetItem::MarginBottom)); + if (stylesPar->ldepth) { + if (item->displayMode() == Q3StyleSheetItem::DisplayListItem) + m /= stylesPar->ldepth * stylesPar->ldepth; + else + m = 0; + } + } + for (i = (int)curStyle->size() - 2 ; i >= 0; --i) { + item = (*curStyle)[i]; + if (nextStyle && i < (int) nextStyle->size() && + ( item->displayMode() == Q3StyleSheetItem::DisplayBlock && + (*nextStyle)[i] == item)) + break; + // emulate CSS2' standard 0 vertical margin for multiple ul or ol tags + if (item->listStyle() != Q3StyleSheetItem::ListStyleUndefined && + (( i> 0 && (*curStyle)[i-1] == item) || (*curStyle)[i+1] == item)) + continue; + mar = qMax(0, item->margin(Q3StyleSheetItem::MarginBottom)); + m = qMax(m, mar); + } + stylesPar->ubm = m - stylesPar->bottomMargin(); + + // do the left margin, simplyfied + item = mainStyle; + if (stylesPar->ulm > 0) { + m = stylesPar->ulm-1; + stylesPar->ulm = 0; + } else { + m = qMax(0, item->margin(Q3StyleSheetItem::MarginLeft)); + } + for (i = (int)curStyle->size() - 2 ; i >= 0; --i) { + item = (*curStyle)[i]; + m += qMax(0, item->margin(Q3StyleSheetItem::MarginLeft)); + } + stylesPar->ulm = m - stylesPar->leftMargin(); + + // do the right margin, simplyfied + item = mainStyle; + if (stylesPar->urm > 0) { + m = stylesPar->urm-1; + stylesPar->urm = 0; + } else { + m = qMax(0, item->margin(Q3StyleSheetItem::MarginRight)); + } + for (i = (int)curStyle->size() - 2 ; i >= 0; --i) { + item = (*curStyle)[i]; + m += qMax(0, item->margin(Q3StyleSheetItem::MarginRight)); + } + stylesPar->urm = m - stylesPar->rightMargin(); + + // do the first line margin, which really should be called text-indent + item = mainStyle; + if (stylesPar->uflm > 0) { + m = stylesPar->uflm-1; + stylesPar->uflm = 0; + } else { + m = qMax(0, item->margin(Q3StyleSheetItem::MarginFirstLine)); + } + for (i = (int)curStyle->size() - 2 ; i >= 0; --i) { + item = (*curStyle)[i]; + mar = qMax(0, item->margin(Q3StyleSheetItem::MarginFirstLine)); + m = qMax(m, mar); + } + stylesPar->uflm =m - stylesPar->firstLineMargin(); + + // do the bogus line "spacing", which really is just an extra margin + item = mainStyle; + for (i = (int)curStyle->size() - 1 ; i >= 0; --i) { + item = (*curStyle)[i]; + if (item->lineSpacing() != Q3StyleSheetItem::Undefined) { + stylesPar->ulinespacing = item->lineSpacing(); + if (formatCollection() && + stylesPar->ulinespacing < formatCollection()->defaultFormat()->height()) + stylesPar->ulinespacing += formatCollection()->defaultFormat()->height(); + break; + } + } + + stylesPar = stylesPar->next(); + prevStyle = curStyle; + curStyle = nextStyle; + nextStyle = (++stylesIndex) < styles.size() ? styles.at(stylesIndex) : 0; + } +} + +void Q3TextDocument::setText(const QString &text, const QString &context) +{ + focusIndicator.parag = 0; + selections.clear(); + if ((txtFormat == Qt::AutoText && Q3StyleSheet::mightBeRichText(text)) + || txtFormat == Qt::RichText) + setRichText(text, context); + else + setPlainText(text); +} + +QString Q3TextDocument::plainText() const +{ + QString buffer; + QString s; + Q3TextParagraph *p = fParag; + while (p) { + if (!p->mightHaveCustomItems) { + const Q3TextString *ts = p->string(); // workaround VC++ and Borland + s = ts->toString(); // with false we don't fix spaces (nbsp) + } else { + for (int i = 0; i < p->length() - 1; ++i) { +#ifndef QT_NO_TEXTCUSTOMITEM + if (p->at(i)->isCustom()) { + if (p->at(i)->customItem()->isNested()) { + s += QLatin1String("\n"); + Q3TextTable *t = (Q3TextTable*)p->at(i)->customItem(); + QList<Q3TextTableCell *> cells = t->tableCells(); + for (int idx = 0; idx < cells.size(); ++idx) { + Q3TextTableCell *c = cells.at(idx); + s += c->richText()->plainText() + QLatin1String("\n"); + } + s += QLatin1String("\n"); + } + } else +#endif + { + s += p->at(i)->c; + } + } + } + s.remove(s.length() - 1, 1); + if (p->next()) + s += QLatin1String("\n"); + buffer += s; + p = p->next(); + } + return buffer; +} + +static QString align_to_string(int a) +{ + if (a & Qt::AlignRight) + return QLatin1String(" align=\"right\""); + if (a & Qt::AlignHCenter) + return QLatin1String(" align=\"center\""); + if (a & Qt::AlignJustify) + return QLatin1String(" align=\"justify\""); + return QString(); +} + +static QString direction_to_string(int dir) +{ + if (dir != QChar::DirON) + return (dir == QChar::DirL? QLatin1String(" dir=\"ltr\"") : QLatin1String(" dir=\"rtl\"")); + return QString(); +} + +static QString list_value_to_string(int v) +{ + if (v != -1) + return QLatin1String(" listvalue=\"") + QString::number(v) + QLatin1Char('"'); + return QString(); +} + +static QString list_style_to_string(int v) +{ + switch(v) { + case Q3StyleSheetItem::ListDecimal: return QLatin1String("\"1\""); + case Q3StyleSheetItem::ListLowerAlpha: return QLatin1String("\"a\""); + case Q3StyleSheetItem::ListUpperAlpha: return QLatin1String("\"A\""); + case Q3StyleSheetItem::ListDisc: return QLatin1String("\"disc\""); + case Q3StyleSheetItem::ListSquare: return QLatin1String("\"square\""); + case Q3StyleSheetItem::ListCircle: return QLatin1String("\"circle\""); + default: + return QString(); + } +} + +static inline bool list_is_ordered(int v) +{ + return v == Q3StyleSheetItem::ListDecimal || + v == Q3StyleSheetItem::ListLowerAlpha || + v == Q3StyleSheetItem::ListUpperAlpha; +} + + +static QString margin_to_string(Q3StyleSheetItem* style, int t, int b, int l, int r, int fl) +{ + QString s; + if (l > 0) + s += QString(s.size() ? QLatin1String(";") : QLatin1String("")) + QLatin1String("margin-left:") + + QString::number(l+qMax(0,style->margin(Q3StyleSheetItem::MarginLeft))) + QLatin1String("px"); + if (r > 0) + s += QString(s.size() ? QLatin1String(";") : QLatin1String("")) + QLatin1String("margin-right:") + + QString::number(r+qMax(0,style->margin(Q3StyleSheetItem::MarginRight))) + QLatin1String("px"); + if (t > 0) + s += QString(s.size() ? QLatin1String(";") : QLatin1String("")) + QLatin1String("margin-top:") + + QString::number(t+qMax(0,style->margin(Q3StyleSheetItem::MarginTop))) + QLatin1String("px"); + if (b > 0) + s += QString(s.size() ? QLatin1String(";") : QLatin1String("")) + QLatin1String("margin-bottom:") + + QString::number(b+qMax(0,style->margin(Q3StyleSheetItem::MarginBottom))) + QLatin1String("px"); + if (fl > 0) + s += QString(s.size() ? QLatin1String(";") : QLatin1String("")) + QLatin1String("text-indent:") + + QString::number(fl+qMax(0,style->margin(Q3StyleSheetItem::MarginFirstLine))) + QLatin1String("px"); + if (s.size()) + return QLatin1String(" style=\"") + s + QLatin1String("\""); + return QString(); +} + +QString Q3TextDocument::richText() const +{ + QString s = QLatin1String(""); + if (!par) { + s += QLatin1String("<html><head><meta name=\"qrichtext\" content=\"1\" /></head><body style=\"font-size:"); + s += QString::number(formatCollection()->defaultFormat()->font().pointSize()); + s += QLatin1String("pt;font-family:"); + s += formatCollection()->defaultFormat()->font().family(); + s += QLatin1String("\">"); + } + Q3TextParagraph* p = fParag; + + Q3StyleSheetItem* item_p = styleSheet()->item(QLatin1String("p")); + Q3StyleSheetItem* item_div = styleSheet()->item(QLatin1String("div")); + Q3StyleSheetItem* item_ul = styleSheet()->item(QLatin1String("ul")); + Q3StyleSheetItem* item_ol = styleSheet()->item(QLatin1String("ol")); + Q3StyleSheetItem* item_li = styleSheet()->item(QLatin1String("li")); + if (!item_p || !item_div || !item_ul || !item_ol || !item_li) { + qWarning("QTextEdit: cannot export HTML due to insufficient stylesheet (lack of p, div, ul, ol, or li)"); + return QString(); + } + int pastListDepth = 0; + int listDepth = 0; +#if 0 + int futureListDepth = 0; +#endif + QVector<int> listStyles(10); + + while (p) { + listDepth = p->listDepth(); + if (listDepth < pastListDepth) { + for (int i = pastListDepth; i > listDepth; i--) + s += list_is_ordered(listStyles[i]) ? QLatin1String("</ol>") : QLatin1String("</ul>"); + s += QLatin1Char('\n'); + } else if (listDepth > pastListDepth) { + s += QLatin1Char('\n'); + listStyles.resize(qMax((int)listStyles.size(), listDepth+1)); + QString list_type; + listStyles[listDepth] = p->listStyle(); + if (!list_is_ordered(p->listStyle()) || item_ol->listStyle() != p->listStyle()) + list_type = QLatin1String(" type=") + list_style_to_string(p->listStyle()); + for (int i = pastListDepth; i < listDepth; i++) { + s += list_is_ordered(p->listStyle()) ? QLatin1String("<ol") : QLatin1String("<ul"); + s += list_type + QLatin1String(">"); + } + } else { + s += QLatin1Char('\n'); + } + + QString ps = p->richText(); + +#if 0 + // for the bottom margin we need to know whether we are at the end of a list + futureListDepth = 0; + if (listDepth > 0 && p->next()) + futureListDepth = p->next()->listDepth(); +#endif + + if (richTextExportStart && richTextExportStart->paragraph() ==p && + richTextExportStart->index() == 0) + s += QLatin1String("<!--StartFragment-->"); + + if (p->isListItem()) { + s += QLatin1String("<li"); + if (p->listStyle() != listStyles[listDepth]) + s += QLatin1String(" type=") + list_style_to_string(p->listStyle()); + s += align_to_string(p->alignment()); + s += margin_to_string(item_li, p->utm, p->ubm, p->ulm, p->urm, p->uflm); + s += list_value_to_string(p->listValue()); + s += direction_to_string(p->direction()); + s += QLatin1String(">"); + s += ps; + s += QLatin1String("</li>"); + } else if (p->listDepth()) { + s += QLatin1String("<div"); + s += align_to_string(p->alignment()); + s += margin_to_string(item_div, p->utm, p->ubm, p->ulm, p->urm, p->uflm); + s += direction_to_string(p->direction()); + s += QLatin1String(">"); + s += ps; + s += QLatin1String("</div>"); + } else { + // normal paragraph item + s += QLatin1String("<p"); + s += align_to_string(p->alignment()); + s += margin_to_string(item_p, p->utm, p->ubm, p->ulm, p->urm, p->uflm); + s += direction_to_string(p->direction()); + s += QLatin1String(">"); + s += ps; + s += QLatin1String("</p>"); + } + pastListDepth = listDepth; + p = p->next(); + } + while (listDepth > 0) { + s += list_is_ordered(listStyles[listDepth]) ? QLatin1String("</ol>") : QLatin1String("</ul>"); + listDepth--; + } + + if (!par) + s += QLatin1String("\n</body></html>\n"); + + return s; +} + +QString Q3TextDocument::text() const +{ + if ((txtFormat == Qt::AutoText && preferRichText) || txtFormat == Qt::RichText) + return richText(); + return plainText(); +} + +QString Q3TextDocument::text(int parag) const +{ + Q3TextParagraph *p = paragAt(parag); + if (!p) + return QString(); + + if ((txtFormat == Qt::AutoText && preferRichText) || txtFormat == Qt::RichText) + return p->richText(); + else + return p->string()->toString(); +} + +void Q3TextDocument::invalidate() +{ + Q3TextParagraph *s = fParag; + while (s) { + s->invalidate(0); + s = s->next(); + } +} + +void Q3TextDocument::selectionStart(int id, int ¶gId, int &index) +{ + QMap<int, Q3TextDocumentSelection>::Iterator it = selections.find(id); + if (it == selections.end()) + return; + Q3TextDocumentSelection &sel = *it; + paragId = !sel.swapped ? sel.startCursor.paragraph()->paragId() : sel.endCursor.paragraph()->paragId(); + index = !sel.swapped ? sel.startCursor.index() : sel.endCursor.index(); +} + +Q3TextCursor Q3TextDocument::selectionStartCursor(int id) +{ + QMap<int, Q3TextDocumentSelection>::Iterator it = selections.find(id); + if (it == selections.end()) + return Q3TextCursor(this); + Q3TextDocumentSelection &sel = *it; + if (sel.swapped) + return sel.endCursor; + return sel.startCursor; +} + +Q3TextCursor Q3TextDocument::selectionEndCursor(int id) +{ + QMap<int, Q3TextDocumentSelection>::Iterator it = selections.find(id); + if (it == selections.end()) + return Q3TextCursor(this); + Q3TextDocumentSelection &sel = *it; + if (!sel.swapped) + return sel.endCursor; + return sel.startCursor; +} + +void Q3TextDocument::selectionEnd(int id, int ¶gId, int &index) +{ + QMap<int, Q3TextDocumentSelection>::Iterator it = selections.find(id); + if (it == selections.end()) + return; + Q3TextDocumentSelection &sel = *it; + paragId = sel.swapped ? sel.startCursor.paragraph()->paragId() : sel.endCursor.paragraph()->paragId(); + index = sel.swapped ? sel.startCursor.index() : sel.endCursor.index(); +} + +void Q3TextDocument::addSelection(int id) +{ + nSelections = qMax(nSelections, id + 1); +} + +static void setSelectionEndHelper(int id, Q3TextDocumentSelection &sel, Q3TextCursor &start, Q3TextCursor &end) +{ + Q3TextCursor c1 = start; + Q3TextCursor c2 = end; + if (sel.swapped) { + c1 = end; + c2 = start; + } + + c1.paragraph()->removeSelection(id); + c2.paragraph()->removeSelection(id); + if (c1.paragraph() != c2.paragraph()) { + c1.paragraph()->setSelection(id, c1.index(), c1.paragraph()->length() - 1); + c2.paragraph()->setSelection(id, 0, c2.index()); + } else { + c1.paragraph()->setSelection(id, qMin(c1.index(), c2.index()), qMax(c1.index(), c2.index())); + } + + sel.startCursor = start; + sel.endCursor = end; + if (sel.startCursor.paragraph() == sel.endCursor.paragraph()) + sel.swapped = sel.startCursor.index() > sel.endCursor.index(); +} + +bool Q3TextDocument::setSelectionEnd(int id, const Q3TextCursor &cursor) +{ + QMap<int, Q3TextDocumentSelection>::Iterator it = selections.find(id); + if (it == selections.end()) + return false; + Q3TextDocumentSelection &sel = *it; + + Q3TextCursor start = sel.startCursor; + Q3TextCursor end = cursor; + + if (start == end) { + removeSelection(id); + setSelectionStart(id, cursor); + return true; + } + + if (sel.endCursor.paragraph() == end.paragraph()) { + setSelectionEndHelper(id, sel, start, end); + return true; + } + + bool inSelection = false; + Q3TextCursor c(this); + Q3TextCursor tmp = sel.startCursor; + if (sel.swapped) + tmp = sel.endCursor; + tmp.restoreState(); + Q3TextCursor tmp2 = cursor; + tmp2.restoreState(); + c.setParagraph(tmp.paragraph()->paragId() < tmp2.paragraph()->paragId() ? tmp.paragraph() : tmp2.paragraph()); + bool hadStart = false; + bool hadEnd = false; + bool hadStartParag = false; + bool hadEndParag = false; + bool hadOldStart = false; + bool hadOldEnd = false; + bool leftSelection = false; + sel.swapped = false; + for (;;) { + if (c == start) + hadStart = true; + if (c == end) + hadEnd = true; + if (c.paragraph() == start.paragraph()) + hadStartParag = true; + if (c.paragraph() == end.paragraph()) + hadEndParag = true; + if (c == sel.startCursor) + hadOldStart = true; + if (c == sel.endCursor) + hadOldEnd = true; + + if (!sel.swapped && + ((hadEnd && !hadStart) + || (hadEnd && hadStart && start.paragraph() == end.paragraph() && start.index() > end.index()))) + sel.swapped = true; + + if ((c == end && hadStartParag) || (c == start && hadEndParag)) { + Q3TextCursor tmp = c; + tmp.restoreState(); + if (tmp.paragraph() != c.paragraph()) { + int sstart = tmp.paragraph()->selectionStart(id); + tmp.paragraph()->removeSelection(id); + tmp.paragraph()->setSelection(id, sstart, tmp.index()); + } + } + + if (inSelection && + ((c == end && hadStart) || (c == start && hadEnd))) + leftSelection = true; + else if (!leftSelection && !inSelection && (hadStart || hadEnd)) + inSelection = true; + + bool noSelectionAnymore = hadOldStart && hadOldEnd && leftSelection && !inSelection && !c.paragraph()->hasSelection(id) && c.atParagEnd(); + c.paragraph()->removeSelection(id); + if (inSelection) { + if (c.paragraph() == start.paragraph() && start.paragraph() == end.paragraph()) { + c.paragraph()->setSelection(id, qMin(start.index(), end.index()), qMax(start.index(), end.index())); + } else if (c.paragraph() == start.paragraph() && !hadEndParag) { + c.paragraph()->setSelection(id, start.index(), c.paragraph()->length() - 1); + } else if (c.paragraph() == end.paragraph() && !hadStartParag) { + c.paragraph()->setSelection(id, end.index(), c.paragraph()->length() - 1); + } else if (c.paragraph() == end.paragraph() && hadEndParag) { + c.paragraph()->setSelection(id, 0, end.index()); + } else if (c.paragraph() == start.paragraph() && hadStartParag) { + c.paragraph()->setSelection(id, 0, start.index()); + } else { + c.paragraph()->setSelection(id, 0, c.paragraph()->length() - 1); + } + } + + if (leftSelection) + inSelection = false; + + if (noSelectionAnymore) + break; + // *ugle*hack optimization + Q3TextParagraph *p = c.paragraph(); + if ( p->mightHaveCustomItems || p == start.paragraph() || p == end.paragraph() || p == lastParagraph()) { + c.gotoNextLetter(); + if (p == lastParagraph() && c.atParagEnd()) + break; + } else { + if (p->document()->parent()) + do { + c.gotoNextLetter(); + } while (c.paragraph() == p); + else + c.setParagraph(p->next()); + } + } + + if (!sel.swapped) + sel.startCursor.paragraph()->setSelection(id, sel.startCursor.index(), sel.startCursor.paragraph()->length() - 1); + + sel.startCursor = start; + sel.endCursor = end; + if (sel.startCursor.paragraph() == sel.endCursor.paragraph()) + sel.swapped = sel.startCursor.index() > sel.endCursor.index(); + + setSelectionEndHelper(id, sel, start, end); + + return true; +} + +void Q3TextDocument::selectAll(int id) +{ + removeSelection(id); + + Q3TextDocumentSelection sel; + sel.swapped = false; + Q3TextCursor c(this); + + c.setParagraph(fParag); + c.setIndex(0); + sel.startCursor = c; + + c.setParagraph(lParag); + c.setIndex(lParag->length() - 1); + sel.endCursor = c; + + selections.insert(id, sel); + + Q3TextParagraph *p = fParag; + while (p) { + p->setSelection(id, 0, p->length() - 1); + p = p->next(); + } + + for (int idx = 0; idx < childList.size(); ++idx) { + Q3TextDocument *dc = childList.at(idx); + dc->selectAll(id); + } +} + +bool Q3TextDocument::removeSelection(int id) +{ + if (!selections.contains(id)) + return false; + + Q3TextDocumentSelection &sel = selections[id]; + + Q3TextCursor start = sel.swapped ? sel.endCursor : sel.startCursor; + Q3TextCursor end = sel.swapped ? sel.startCursor : sel.endCursor; + Q3TextParagraph* p = 0; + while (start != end) { + if (p != start.paragraph()) { + p = start.paragraph(); + p->removeSelection(id); + //### avoid endless loop by all means necessary, did somebody mention refactoring? + if (!parent() && p == lParag) + break; + } + start.gotoNextLetter(); + } + p = start.paragraph(); + p->removeSelection(id); + selections.remove(id); + return true; +} + +QString Q3TextDocument::selectedText(int id, bool asRichText) const +{ + QMap<int, Q3TextDocumentSelection>::ConstIterator it = selections.find(id); + if (it == selections.end()) + return QString(); + + Q3TextDocumentSelection sel = *it; + + + Q3TextCursor c1 = sel.startCursor; + Q3TextCursor c2 = sel.endCursor; + if (sel.swapped) { + c2 = sel.startCursor; + c1 = sel.endCursor; + } + + /* 3.0.3 improvement: Make it possible to get a reasonable + selection inside a table. This approach is very conservative: + make sure that both cursors have the same depth level and point + to paragraphs within the same text document. + + Meaning if you select text in two table cells, you will get the + entire table. This is still far better than the 3.0.2, where + you always got the entire table. + + ### Fix this properly when refactoring + */ + while (c2.nestedDepth() > c1.nestedDepth()) + c2.oneUp(); + while (c1.nestedDepth() > c2.nestedDepth()) + c1.oneUp(); + while (c1.nestedDepth() && c2.nestedDepth() && + c1.paragraph()->document() != c2.paragraph()->document()) { + c1.oneUp(); + c2.oneUp(); + } + // do not trust sel_swapped with tables. Fix this properly when refactoring as well + if (c1.paragraph()->paragId() > c2.paragraph()->paragId() || + (c1.paragraph() == c2.paragraph() && c1.index() > c2.index())) { + Q3TextCursor tmp = c1; + c2 = c1; + c1 = tmp; + } + + // end selection 3.0.3 improvement + + if (asRichText && !parent()) { + richTextExportStart = &c1; + richTextExportEnd = &c2; + + QString sel = richText(); + int from = sel.indexOf(QLatin1String("<!--StartFragment-->")); + if (from >= 0) { + from += 20; + // find the previous span and move it into the start fragment before we clip it + QString prevspan; + int pspan = sel.lastIndexOf(QLatin1String("<span"), from-21); + if (pspan > sel.lastIndexOf(QLatin1String("</span"), from-21)) { + int spanend = sel.indexOf(QLatin1Char('>'), pspan); + prevspan = sel.mid(pspan, spanend - pspan + 1); + } + int to = sel.lastIndexOf(QLatin1String("<!--EndFragment-->")); + if (from <= to) + sel = QLatin1String("<!--StartFragment-->") + prevspan + sel.mid(from, to - from); + } + richTextExportStart = richTextExportEnd = 0; + return sel; + } + + QString s; + if (c1.paragraph() == c2.paragraph()) { + Q3TextParagraph *p = c1.paragraph(); + int end = c2.index(); + if (p->at(qMax(0, end - 1))->isCustom()) + ++end; + if (!p->mightHaveCustomItems) { + s += p->string()->toString().mid(c1.index(), end - c1.index()); + } else { + for (int i = c1.index(); i < end; ++i) { +#ifndef QT_NO_TEXTCUSTOMITEM + if (p->at(i)->isCustom()) { + if (p->at(i)->customItem()->isNested()) { + s += QLatin1String("\n"); + Q3TextTable *t = (Q3TextTable*)p->at(i)->customItem(); + QList<Q3TextTableCell *> cells = t->tableCells(); + for (int idx = 0; idx < cells.size(); ++idx) { + Q3TextTableCell *c = cells.at(idx); + s += c->richText()->plainText() + QLatin1String("\n"); + } + s += QLatin1String("\n"); + } + } else +#endif + { + s += p->at(i)->c; + } + } + } + } else { + Q3TextParagraph *p = c1.paragraph(); + int start = c1.index(); + while (p) { + int end = p == c2.paragraph() ? c2.index() : p->length() - 1; + if (p == c2.paragraph() && p->at(qMax(0, end - 1))->isCustom()) + ++end; + if (!p->mightHaveCustomItems) { + s += p->string()->toString().mid(start, end - start); + if (p != c2.paragraph()) + s += QLatin1String("\n"); + } else { + for (int i = start; i < end; ++i) { +#ifndef QT_NO_TEXTCUSTOMITEM + if (p->at(i)->isCustom()) { + if (p->at(i)->customItem()->isNested()) { + s += QLatin1String(QLatin1String("\n")); + Q3TextTable *t = (Q3TextTable*)p->at(i)->customItem(); + QList<Q3TextTableCell *> cells = t->tableCells(); + for (int idx = 0; idx < cells.size(); ++idx) { + Q3TextTableCell *c = cells.at(idx); + s += c->richText()->plainText() + QLatin1String("\n"); + } + s += QLatin1String("\n"); + } + } else +#endif + { + s += p->at(i)->c; + } + } + } + start = 0; + if (p == c2.paragraph()) + break; + p = p->next(); + } + } + // ### workaround for plain text export until we get proper + // mime types: turn unicode line seperators into the more + // widely understood \n. Makes copy and pasting code snipplets + // from within Assistent possible + QChar* uc = (QChar*) s.unicode(); + for (int ii = 0; ii < s.length(); ii++) { + if (uc[(int)ii] == QChar::LineSeparator) + uc[(int)ii] = QLatin1Char('\n'); + else if ( uc[(int)ii] == QChar::Nbsp ) + uc[(int)ii] = QLatin1Char(' '); + } + return s; +} + +void Q3TextDocument::setFormat(int id, Q3TextFormat *f, int flags) +{ + QMap<int, Q3TextDocumentSelection>::ConstIterator it = selections.constFind(id); + if (it == selections.constEnd()) + return; + + Q3TextDocumentSelection sel = *it; + + Q3TextCursor c1 = sel.startCursor; + Q3TextCursor c2 = sel.endCursor; + if (sel.swapped) { + c2 = sel.startCursor; + c1 = sel.endCursor; + } + + c2.restoreState(); + c1.restoreState(); + + if (c1.paragraph() == c2.paragraph()) { + c1.paragraph()->setFormat(c1.index(), c2.index() - c1.index(), f, true, flags); + return; + } + + c1.paragraph()->setFormat(c1.index(), c1.paragraph()->length() - c1.index(), f, true, flags); + Q3TextParagraph *p = c1.paragraph()->next(); + while (p && p != c2.paragraph()) { + p->setFormat(0, p->length(), f, true, flags); + p = p->next(); + } + c2.paragraph()->setFormat(0, c2.index(), f, true, flags); +} + +void Q3TextDocument::removeSelectedText(int id, Q3TextCursor *cursor) +{ + QMap<int, Q3TextDocumentSelection>::Iterator it = selections.find(id); + if (it == selections.end()) + return; + + Q3TextDocumentSelection sel = *it; + Q3TextCursor c1 = sel.startCursor; + Q3TextCursor c2 = sel.endCursor; + if (sel.swapped) { + c2 = sel.startCursor; + c1 = sel.endCursor; + } + + // ### no support for editing tables yet + if (c1.nestedDepth() || c2.nestedDepth()) + return; + + c2.restoreState(); + c1.restoreState(); + + *cursor = c1; + removeSelection(id); + + if (c1.paragraph() == c2.paragraph()) { + c1.paragraph()->remove(c1.index(), c2.index() - c1.index()); + return; + } + + if (c1.paragraph() == fParag && c1.index() == 0 && + c2.paragraph() == lParag && c2.index() == lParag->length() - 1) + cursor->setValid(false); + + bool didGoLeft = false; + if ( c1.index() == 0 && c1.paragraph() != fParag) { + cursor->gotoPreviousLetter(); + didGoLeft = cursor->isValid(); + } + + c1.paragraph()->remove(c1.index(), c1.paragraph()->length() - 1 - c1.index()); + Q3TextParagraph *p = c1.paragraph()->next(); + int dy = 0; + Q3TextParagraph *tmp; + while (p && p != c2.paragraph()) { + tmp = p->next(); + dy -= p->rect().height(); + delete p; + p = tmp; + } + c2.paragraph()->remove(0, c2.index()); + while (p) { + p->move(dy); + p->invalidate(0); + p->setEndState(-1); + p = p->next(); + } + + + c1.paragraph()->join(c2.paragraph()); + + if (didGoLeft) + cursor->gotoNextLetter(); +} + +void Q3TextDocument::indentSelection(int id) +{ + QMap<int, Q3TextDocumentSelection>::Iterator it = selections.find(id); + if (it == selections.end()) + return; + + Q3TextDocumentSelection sel = *it; + Q3TextParagraph *startParag = sel.startCursor.paragraph(); + Q3TextParagraph *endParag = sel.endCursor.paragraph(); + if (sel.endCursor.paragraph()->paragId() < sel.startCursor.paragraph()->paragId()) { + endParag = sel.startCursor.paragraph(); + startParag = sel.endCursor.paragraph(); + } + + Q3TextParagraph *p = startParag; + while (p && p != endParag) { + p->indent(); + p = p->next(); + } +} + +void Q3TextCommandHistory::clear() +{ + while (!history.isEmpty()) + delete history.takeFirst(); + current = -1; +} + +void Q3TextDocument::addCommand(Q3TextCommand *cmd) +{ + commandHistory->addCommand(cmd); +} + +Q3TextCursor *Q3TextDocument::undo(Q3TextCursor *c) +{ + return commandHistory->undo(c); +} + +Q3TextCursor *Q3TextDocument::redo(Q3TextCursor *c) +{ + return commandHistory->redo(c); +} + +bool Q3TextDocument::find(Q3TextCursor& cursor, const QString &expr, bool cs, bool wo, bool forward) +{ + Qt::CaseSensitivity caseSensitive = cs ? Qt::CaseSensitive : Qt::CaseInsensitive; + removeSelection(Standard); + if (expr.isEmpty()) + return false; + for (;;) { + QString s = cursor.paragraph()->string()->toString(); + int start = cursor.index(); + for (;;) { + int res = forward + ? s.indexOf(expr, start, caseSensitive) + : s.lastIndexOf(expr, start, caseSensitive); + int end = res + expr.length(); + if (res == -1 || (!forward && start <= res)) + break; + if (!wo || ((res == 0 || !s[res-1].isLetterOrNumber()) + && (end == (int)s.length() || !s[end].isLetterOrNumber()))) { + removeSelection(Standard); + cursor.setIndex(forward ? end : res); + setSelectionStart(Standard, cursor); + cursor.setIndex(forward ? res : end); + setSelectionEnd(Standard, cursor); + if (!forward) + cursor.setIndex(res); + return true; + } + start = res + (forward ? 1 : -1); + } + if (forward) { + if (cursor.paragraph() == lastParagraph() && cursor.atParagEnd()) + break; + cursor.gotoNextLetter(); + } else { + if (cursor.paragraph() == firstParagraph() && cursor.atParagStart()) + break; + cursor.gotoPreviousLetter(); + } + } + return false; +} + +void Q3TextDocument::setTextFormat(Qt::TextFormat f) +{ + txtFormat = f; + if (fParag == lParag && fParag->length() <= 1) + fParag->rtext = (f == Qt::RichText); +} + +Qt::TextFormat Q3TextDocument::textFormat() const +{ + return txtFormat; +} + +bool Q3TextDocument::inSelection(int selId, const QPoint &pos) const +{ + QMap<int, Q3TextDocumentSelection>::ConstIterator it = selections.find(selId); + if (it == selections.end()) + return false; + + Q3TextDocumentSelection sel = *it; + Q3TextParagraph *startParag = sel.startCursor.paragraph(); + Q3TextParagraph *endParag = sel.endCursor.paragraph(); + if (sel.startCursor.paragraph() == sel.endCursor.paragraph() && + sel.startCursor.paragraph()->selectionStart(selId) == sel.endCursor.paragraph()->selectionEnd(selId)) + return false; + if (sel.endCursor.paragraph()->paragId() < sel.startCursor.paragraph()->paragId()) { + endParag = sel.startCursor.paragraph(); + startParag = sel.endCursor.paragraph(); + } + + Q3TextParagraph *p = startParag; + while (p) { + if (p->rect().contains(pos)) { + bool inSel = false; + int selStart = p->selectionStart(selId); + int selEnd = p->selectionEnd(selId); + int y = 0; + int h = 0; + for (int i = 0; i < p->length(); ++i) { + if (i == selStart) + inSel = true; + if (i == selEnd) + break; + if (p->at(i)->lineStart) { + y = (*p->lineStarts.find(i))->y; + h = (*p->lineStarts.find(i))->h; + } + if (pos.y() - p->rect().y() >= y && pos.y() - p->rect().y() <= y + h) { + if (inSel && pos.x() >= p->at(i)->x && + pos.x() <= p->at(i)->x + p->at(i)->format()->width(p->at(i)->c)) + return true; + } + } + } + if (pos.y() < p->rect().y()) + break; + if (p == endParag) + break; + p = p->next(); + } + + return false; +} + +void Q3TextDocument::doLayout(QPainter *p, int w) +{ + minw = wused = 0; + if (!is_printer(p)) + p = 0; + withoutDoubleBuffer = (p != 0); + QPainter * oldPainter = Q3TextFormat::painter(); + Q3TextFormat::setPainter(p); + tStopWidth = formatCollection()->defaultFormat()->width( QLatin1Char('x') ) * 8; + flow_->setWidth(w); + cw = w; + vw = w; + Q3TextParagraph *parag = fParag; + while (parag) { + parag->invalidate(0); + if (p) + parag->adjustToPainter(p); + parag->format(); + parag = parag->next(); + } + Q3TextFormat::setPainter(oldPainter); +} + +QPixmap *Q3TextDocument::bufferPixmap(const QSize &s) +{ + if (!buf_pixmap) + buf_pixmap = new QPixmap(s.expandedTo(QSize(1,1))); + else if (buf_pixmap->size() != s) + buf_pixmap->resize(s.expandedTo(buf_pixmap->size())); + return buf_pixmap; +} + +void Q3TextDocument::draw(QPainter *p, const QRect &rect, const QPalette &pal, + const QBrush *paper) +{ + if (!firstParagraph()) + return; + + if (paper) { + p->setBrushOrigin(-qIntCast(p->translationX()), + -qIntCast(p->translationY())); + + p->fillRect(rect, *paper); + } + + QPainter * oldPainter = Q3TextFormat::painter(); + Q3TextFormat::setPainter(p); + + if (formatCollection()->defaultFormat()->color() != pal.text().color()) + setDefaultFormat(formatCollection()->defaultFormat()->font(), pal.text().color()); + + Q3TextParagraph *parag = firstParagraph(); + while (parag) { + if (!parag->isValid()) + parag->format(); + int y = parag->rect().y(); + QRect pr(parag->rect()); + pr.setX(0); + pr.setWidth(QWIDGETSIZE_MAX); + if (!rect.isNull() && !rect.intersects(pr)) { + parag = parag->next(); + continue; + } + p->translate(0, y); + if (rect.isValid()) + parag->paint(*p, pal, 0, false, rect.x(), rect.y(), rect.width(), rect.height()); + else + parag->paint(*p, pal, 0, false); + p->translate(0, -y); + parag = parag->next(); + if (!flow()->isEmpty()) + flow()->drawFloatingItems(p, rect.x(), rect.y(), rect.width(), rect.height(), pal, false); + } + Q3TextFormat::setPainter(oldPainter); +} + +void Q3TextDocument::drawParagraph(QPainter *painter, Q3TextParagraph *parag, int cx, int cy, + int cw, int ch, + QPixmap *&/*doubleBuffer*/, const QPalette &pal, + bool drawCursor, Q3TextCursor *cursor, bool resetChanged) +{ + if (resetChanged) + parag->setChanged(false); + QRect ir(parag->rect()); +#ifndef QT_NO_TEXTCUSTOMITEM + if (!parag->tableCell()) +#endif + ir.setWidth(width()); + + painter->translate(ir.x(), ir.y()); + + if (!parag->document()->parent()) { + const QPoint oldOrigin = painter->brushOrigin(); + painter->setBrushOrigin(-ir.topLeft()); + painter->fillRect(QRect(0, 0, ir.width(), ir.height()), parag->backgroundBrush(pal)); + painter->setBrushOrigin(oldOrigin); + } + + painter->translate(-(ir.x() - parag->rect().x()), + -(ir.y() - parag->rect().y())); + parag->paint(*painter, pal, drawCursor ? cursor : 0, true, cx, cy, cw, ch); + + painter->translate(-ir.x(), -ir.y()); + + parag->document()->nextDoubleBuffered = false; +} + +Q3TextParagraph *Q3TextDocument::draw(QPainter *p, int cx, int cy, int cw, int ch, + const QPalette &pal, bool onlyChanged, bool drawCursor, + Q3TextCursor *cursor, bool resetChanged) +{ + if (withoutDoubleBuffer || (par && par->withoutDoubleBuffer)) { + withoutDoubleBuffer = true; + QRect r; + draw(p, r, pal); + return 0; + } + withoutDoubleBuffer = false; + + if (!firstParagraph()) + return 0; + + QPainter * oldPainter = Q3TextFormat::painter(); + Q3TextFormat::setPainter(p); + if (formatCollection()->defaultFormat()->color() != pal.text().color()) + setDefaultFormat(formatCollection()->defaultFormat()->font(), pal.text().color()); + + if (cx < 0 && cy < 0) { + cx = 0; + cy = 0; + cw = width(); + ch = height(); + } + + Q3TextParagraph *lastFormatted = 0; + Q3TextParagraph *parag = firstParagraph(); + + QPixmap *doubleBuffer = 0; + + while (parag) { + lastFormatted = parag; + if (!parag->isValid()) + parag->format(); + + QRect pr = parag->rect(); + pr.setWidth(parag->document()->width()); + if (pr.y() > cy + ch) + goto floating; + QRect clipr(cx, cy, cw, ch); + if (!pr.intersects(clipr) || (onlyChanged && !parag->hasChanged())) { + pr.setWidth(parag->document()->width()); + parag = parag->next(); + continue; + } + + drawParagraph(p, parag, cx, cy, cw, ch, doubleBuffer, pal, drawCursor, + cursor, resetChanged); + parag = parag->next(); + } + + parag = lastParagraph(); + + floating: + if (parag->rect().y() + parag->rect().height() < parag->document()->height()) { + if (!parag->document()->parent()) { + QRect fillRect = QRect(0, parag->rect().y() + parag->rect().height(), parag->document()->width(), + parag->document()->height() - (parag->rect().y() + parag->rect().height())); + if (QRect(cx, cy, cw, ch).intersects(fillRect)) + p->fillRect(fillRect, pal.brush(QPalette::Base)); + } + if (!flow()->isEmpty()) { + QRect cr(cx, cy, cw, ch); + flow()->drawFloatingItems(p, cr.x(), cr.y(), cr.width(), cr.height(), pal, false); + } + } + + if (buf_pixmap && buf_pixmap->height() > 300) { + delete buf_pixmap; + buf_pixmap = 0; + } + + Q3TextFormat::setPainter(oldPainter); + return lastFormatted; +} + +/* + #### this function only sets the default font size in the format collection + */ +void Q3TextDocument::setDefaultFormat(const QFont &font, const QColor &color) +{ + bool reformat = font != fCollection->defaultFormat()->font(); + for (int idx = 0; idx < childList.size(); ++idx) { + Q3TextDocument *dc = childList.at(idx); + dc->setDefaultFormat(font, color); + } + fCollection->updateDefaultFormat(font, color, sheet_); + + if (!reformat) + return; + tStopWidth = formatCollection()->defaultFormat()->width(QLatin1Char('x')) * 8; + + // invalidate paragraphs and custom items + Q3TextParagraph *p = fParag; + while (p) { + p->invalidate(0); +#ifndef QT_NO_TEXTCUSTOMITEM + for (int i = 0; i < p->length() - 1; ++i) + if (p->at(i)->isCustom()) + p->at(i)->customItem()->invalidate(); +#endif + p = p->next(); + } +} + + +/*! + \preliminary + + Generates an internal object for the tag called \a name, given the + attributes \a attr, and using additional information provided by + the mime source factory \a factory. + + \a context is the optional context of the document, i.e. the path + to look for relative links. This becomes important if the text + contains relative references, for example within image tags. + QSimpleRichText always uses the default mime source factory (see + \l{Q3MimeSourceFactory::defaultFactory()}) to resolve these + references. The context will then be used to calculate the + absolute path. See Q3MimeSourceFactory::makeAbsolute() for details. + + \a emptyTag and \a doc are for internal use only. + + This function should not be used in application code. +*/ +#ifndef QT_NO_TEXTCUSTOMITEM +Q3TextCustomItem* Q3TextDocument::tag(Q3StyleSheet *sheet, const QString& name, + const QMap<QString, QString> &attr, + const QString& context, + const Q3MimeSourceFactory& factory, + bool /*emptyTag */, Q3TextDocument *doc) +{ + const Q3StyleSheetItem* style = sheet->item(name); + // first some known tags + if (!style) + return 0; + if (style->name() == QLatin1String("img")) + return new Q3TextImage(doc, attr, context, (Q3MimeSourceFactory&)factory); + if (style->name() == QLatin1String("hr")) + return new Q3TextHorizontalLine(doc, attr, context, (Q3MimeSourceFactory&)factory ); + return 0; +} +#endif + + +#ifndef QT_NO_TEXTCUSTOMITEM +void Q3TextDocument::registerCustomItem(Q3TextCustomItem *i, Q3TextParagraph *p) +{ + if (i && i->placement() != Q3TextCustomItem::PlaceInline) { + flow_->registerFloatingItem(i); + p->registerFloatingItem(i); + i->setParagraph(p); + } + p->mightHaveCustomItems = mightHaveCustomItems = true; +} + +void Q3TextDocument::unregisterCustomItem(Q3TextCustomItem *i, Q3TextParagraph *p) +{ + p->unregisterFloatingItem(i); + i->setParagraph(0); + flow_->unregisterFloatingItem(i); +} +#endif + +bool Q3TextDocument::hasFocusParagraph() const +{ + return !!focusIndicator.parag; +} + +QString Q3TextDocument::focusHref() const +{ + return focusIndicator.href; +} + +QString Q3TextDocument::focusName() const +{ + return focusIndicator.name; +} + +bool Q3TextDocument::focusNextPrevChild(bool next) +{ + if (!focusIndicator.parag) { + if (next) { + focusIndicator.parag = fParag; + focusIndicator.start = 0; + focusIndicator.len = 0; + } else { + focusIndicator.parag = lParag; + focusIndicator.start = lParag->length(); + focusIndicator.len = 0; + } + } else { + focusIndicator.parag->setChanged(true); + } + focusIndicator.href.clear(); + focusIndicator.name.clear(); + + if (next) { + Q3TextParagraph *p = focusIndicator.parag; + int index = focusIndicator.start + focusIndicator.len; + while (p) { + for (int i = index; i < p->length(); ++i) { + if (p->at(i)->isAnchor()) { + p->setChanged(true); + focusIndicator.parag = p; + focusIndicator.start = i; + focusIndicator.len = 0; + focusIndicator.href = p->at(i)->anchorHref(); + focusIndicator.name = p->at(i)->anchorName(); + while (i < p->length()) { + if (!p->at(i)->isAnchor()) + return true; + focusIndicator.len++; + i++; + } +#ifndef QT_NO_TEXTCUSTOMITEM + } else if (p->at(i)->isCustom()) { + if (p->at(i)->customItem()->isNested()) { + Q3TextTable *t = (Q3TextTable*)p->at(i)->customItem(); + QList<Q3TextTableCell *> cells = t->tableCells(); + // first try to continue + int idx; + bool resetCells = true; + for (idx = 0; idx < cells.size(); ++idx) { + Q3TextTableCell *c = cells.at(idx); + if (c->richText()->hasFocusParagraph()) { + if (c->richText()->focusNextPrevChild(next)) { + p->setChanged(true); + focusIndicator.parag = p; + focusIndicator.start = i; + focusIndicator.len = 0; + focusIndicator.href = c->richText()->focusHref(); + focusIndicator.name = c->richText()->focusName(); + return true; + } else { + resetCells = false; + ++idx; + break; + } + } + } + // now really try + if (resetCells) + idx = 0; + for (; idx < cells.size(); ++idx) { + Q3TextTableCell *c = cells.at(idx); + if (c->richText()->focusNextPrevChild(next)) { + p->setChanged(true); + focusIndicator.parag = p; + focusIndicator.start = i; + focusIndicator.len = 0; + focusIndicator.href = c->richText()->focusHref(); + focusIndicator.name = c->richText()->focusName(); + return true; + } + } + } +#endif + } + } + index = 0; + p = p->next(); + } + } else { + Q3TextParagraph *p = focusIndicator.parag; + int index = focusIndicator.start - 1; + if (focusIndicator.len == 0 && index < focusIndicator.parag->length() - 1) + index++; + while (p) { + for (int i = index; i >= 0; --i) { + if (p->at(i)->isAnchor()) { + p->setChanged(true); + focusIndicator.parag = p; + focusIndicator.start = i; + focusIndicator.len = 0; + focusIndicator.href = p->at(i)->anchorHref(); + focusIndicator.name = p->at(i)->anchorName(); + while (i >= -1) { + if (i < 0 || !p->at(i)->isAnchor()) { + focusIndicator.start++; + return true; + } + if (i < 0) + break; + focusIndicator.len++; + focusIndicator.start--; + i--; + } +#ifndef QT_NO_TEXTCUSTOMITEM + } else if (p->at(i)->isCustom()) { + if (p->at(i)->customItem()->isNested()) { + Q3TextTable *t = (Q3TextTable*)p->at(i)->customItem(); + QList<Q3TextTableCell *> cells = t->tableCells(); + // first try to continue + int idx; + bool resetCells = true; + for (idx = cells.size()-1; idx >= 0; --idx) { + Q3TextTableCell *c = cells.at(idx); + if (c->richText()->hasFocusParagraph()) { + if (c->richText()->focusNextPrevChild(next)) { + p->setChanged(true); + focusIndicator.parag = p; + focusIndicator.start = i; + focusIndicator.len = 0; + focusIndicator.href = c->richText()->focusHref(); + focusIndicator.name = c->richText()->focusName(); + return true; + } else { + resetCells = false; + --idx; + break; + } + } + } + // now really try + if (resetCells) + idx = cells.size()-1; + for (; idx >= 0; --idx) { + Q3TextTableCell *c = cells.at(idx); + if (c->richText()->focusNextPrevChild(next)) { + p->setChanged(true); + focusIndicator.parag = p; + focusIndicator.start = i; + focusIndicator.len = 0; + focusIndicator.href = c->richText()->focusHref(); + focusIndicator.name = c->richText()->focusName(); + return true; + } + } + } +#endif + } + } + p = p->prev(); + if (p) + index = p->length() - 1; + } + } + + focusIndicator.parag = 0; + + return false; +} + +int Q3TextDocument::length() const +{ + int l = -1; + Q3TextParagraph *p = fParag; + while (p) { + l += p->length(); + p = p->next(); + } + return qMax(0,l); +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +int Q3TextFormat::width(const QChar &c) const +{ + if (c.unicode() == 0xad) // soft hyphen + return 0; + if (!pntr || !pntr->isActive()) { + if (c == QLatin1Char('\t')) + return fm.width(QLatin1Char(' ')); + if (ha == AlignNormal) { + int w; + if (c.row()) + w = fm.width(c); + else + w = widths[c.unicode()]; + if (w == 0 && !c.row()) { + w = fm.width(c); + ((Q3TextFormat*)this)->widths[c.unicode()] = w; + } + return w; + } else { + QFont f(fn); + if (usePixelSizes) + f.setPixelSize((f.pixelSize() * 2) / 3); + else + f.setPointSize((f.pointSize() * 2) / 3); + QFontMetrics fm_(f); + return fm_.width(c); + } + } + + QFont f(fn); + if (ha != AlignNormal) { + if (usePixelSizes) + f.setPixelSize((f.pixelSize() * 2) / 3); + else + f.setPointSize((f.pointSize() * 2) / 3); + } + applyFont(f); + + return pntr_fm->width(c); +} + +int Q3TextFormat::width(const QString &str, int pos) const +{ + int w = 0; + if (str.unicode()[pos].unicode() == 0xad) + return w; + if (!pntr || !pntr->isActive()) { + if (ha == AlignNormal) { + w = fm.charWidth(str, pos); + } else { + QFont f(fn); + if (usePixelSizes) + f.setPixelSize((f.pixelSize() * 2) / 3); + else + f.setPointSize((f.pointSize() * 2) / 3); + QFontMetrics fm_(f); + w = fm_.charWidth(str, pos); + } + } else { + QFont f(fn); + if (ha != AlignNormal) { + if (usePixelSizes) + f.setPixelSize((f.pixelSize() * 2) / 3); + else + f.setPointSize((f.pointSize() * 2) / 3); + } + applyFont(f); + w = pntr_fm->charWidth(str, pos); + } + return w; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Q3TextString::Q3TextString() +{ + bidiDirty = true; + bidi = false; + rightToLeft = false; + dir = QChar::DirON; +} + +Q3TextString::Q3TextString(const Q3TextString &s) +{ + bidiDirty = true; + bidi = s.bidi; + rightToLeft = s.rightToLeft; + dir = s.dir; + data = s.data; + data.detach(); + for (int i = 0; i < (int)data.size(); ++i) { + Q3TextFormat *f = data[i].format(); + if (f) + f->addRef(); + } +} + +void Q3TextString::insert(int index, const QString &s, Q3TextFormat *f) +{ + insert(index, s.unicode(), s.length(), f); +} + +void Q3TextString::insert(int index, const QChar *unicode, int len, Q3TextFormat *f) +{ + int os = data.size(); + data.resize(data.size() + len); + if (index < os) { + memmove(data.data() + index + len, data.data() + index, + sizeof(Q3TextStringChar) * (os - index)); + } + Q3TextStringChar *ch = data.data() + index; + for (int i = 0; i < len; ++i) { + ch->x = 0; + ch->lineStart = 0; + ch->nobreak = false; + ch->type = Q3TextStringChar::Regular; + ch->p.format = f; + ch->rightToLeft = 0; + ch->c = unicode[i]; + ++ch; + } + bidiDirty = true; +} + +Q3TextString::~Q3TextString() +{ + clear(); +} + +void Q3TextString::insert(int index, Q3TextStringChar *c, bool doAddRefFormat ) +{ + int os = data.size(); + data.resize(data.size() + 1); + if (index < os) { + memmove(data.data() + index + 1, data.data() + index, + sizeof(Q3TextStringChar) * (os - index)); + } + Q3TextStringChar &ch = data[(int)index]; + ch.c = c->c; + ch.x = 0; + ch.lineStart = 0; + ch.rightToLeft = 0; + ch.p.format = 0; + ch.type = Q3TextStringChar::Regular; + ch.nobreak = false; + if (doAddRefFormat && c->format()) + c->format()->addRef(); + ch.setFormat(c->format()); + bidiDirty = true; +} + +int Q3TextString::appendParagraphs( Q3TextParagraph *start, Q3TextParagraph *end ) +{ + int paragCount = 0; + int newLength = data.size(); + for (Q3TextParagraph *p = start; p != end; p = p->next()) { + newLength += p->length(); + ++paragCount; + } + + const int oldLength = data.size(); + data.resize(newLength); + + Q3TextStringChar *d = &data[oldLength]; + for (Q3TextParagraph *p = start; p != end; p = p->next()) { + const Q3TextStringChar * const src = p->at(0); + int i = 0; + for (; i < p->length() - 1; ++i) { + d[i].c = src[i].c; + d[i].x = 0; + d[i].lineStart = 0; + d[i].rightToLeft = 0; + d[i].type = Q3TextStringChar::Regular; + d[i].nobreak = false; + d[i].p.format = src[i].format(); + if (d[i].p.format) + d[i].p.format->addRef(); + } + d[i].x = 0; + d[i].lineStart = 0; + d[i].nobreak = false; + d[i].type = Q3TextStringChar::Regular; + d[i].p.format = 0; + d[i].rightToLeft = 0; + d[i].c = QLatin1Char('\n'); + d += p->length(); + } + + bidiDirty = true; + return paragCount; +} + +void Q3TextString::truncate(int index) +{ + index = qMax(index, 0); + index = qMin(index, (int)data.size() - 1); + if (index < (int)data.size()) { + for (int i = index + 1; i < (int)data.size(); ++i) { + Q3TextStringChar &ch = data[i]; +#ifndef QT_NO_TEXTCUSTOMITEM + if (!(ch.type == Q3TextStringChar::Regular)) { + delete ch.customItem(); + if (ch.p.custom->format) + ch.p.custom->format->removeRef(); + delete ch.p.custom; + ch.p.custom = 0; + } else +#endif + if (ch.format()) { + ch.format()->removeRef(); + } + } + } + data.resize(index); + bidiDirty = true; +} + +void Q3TextString::remove(int index, int len) +{ + for (int i = index; i < (int)data.size() && i - index < len; ++i) { + Q3TextStringChar &ch = data[i]; +#ifndef QT_NO_TEXTCUSTOMITEM + if (!(ch.type == Q3TextStringChar::Regular)) { + delete ch.customItem(); + if (ch.p.custom->format) + ch.p.custom->format->removeRef(); + delete ch.p.custom; + ch.p.custom = 0; + } else +#endif + if (ch.format()) { + ch.format()->removeRef(); + } + } + memmove(data.data() + index, data.data() + index + len, + sizeof(Q3TextStringChar) * (data.size() - index - len)); + data.resize(data.size() - len); + bidiDirty = true; +} + +void Q3TextString::clear() +{ + for (int i = 0; i < (int)data.count(); ++i) { + Q3TextStringChar &ch = data[i]; +#ifndef QT_NO_TEXTCUSTOMITEM + if (!(ch.type == Q3TextStringChar::Regular)) { + if (ch.customItem() && ch.customItem()->placement() == Q3TextCustomItem::PlaceInline) + delete ch.customItem(); + if (ch.p.custom->format) + ch.p.custom->format->removeRef(); + delete ch.p.custom; + ch.p.custom = 0; + } else +#endif + if (ch.format()) { + ch.format()->removeRef(); + } + } + data.resize(0); + bidiDirty = true; +} + +void Q3TextString::setFormat(int index, Q3TextFormat *f, bool useCollection) +{ + Q3TextStringChar &ch = data[index]; + if (useCollection && ch.format()) + ch.format()->removeRef(); + ch.setFormat(f); +} + +void Q3TextString::checkBidi() const +{ + // ############ fix BIDI handling + Q3TextString *that = (Q3TextString *)this; + that->bidiDirty = false; + int length = data.size(); + if (!length) { + that->bidi = rightToLeft; + that->rightToLeft = (dir == QChar::DirR); + return; + } + + if (dir == QChar::DirR) { + that->rightToLeft = true; + } else if (dir == QChar::DirL) { + that->rightToLeft = false; + } else { + that->rightToLeft = (QApplication::layoutDirection() == Qt::RightToLeft); + } + + const Q3TextStringChar *start = data.data(); + const Q3TextStringChar *end = start + length; + + ((Q3TextString *)this)->stringCache = toString(data); + + // determines the properties we need for layouting + QTextEngine textEngine; + textEngine.text = toString(); + textEngine.option.setTextDirection(rightToLeft ? Qt::RightToLeft : Qt::LeftToRight); + textEngine.itemize(); + const HB_CharAttributes *ca = textEngine.attributes() + length-1; + Q3TextStringChar *ch = (Q3TextStringChar *)end - 1; + QScriptItem *item = &textEngine.layoutData->items[textEngine.layoutData->items.size()-1]; + unsigned char bidiLevel = item->analysis.bidiLevel; + that->bidi = (bidiLevel || rightToLeft); + int pos = length-1; + while (ch >= start) { + if (item->position > pos) { + --item; + Q_ASSERT(item >= &textEngine.layoutData->items[0]); + bidiLevel = item->analysis.bidiLevel; + if (bidiLevel) + that->bidi = true; + } + ch->softBreak = ca->lineBreakType >= HB_Break; + ch->whiteSpace = ca->whiteSpace; + ch->charStop = ca->charStop; + ch->bidiLevel = bidiLevel; + ch->rightToLeft = (bidiLevel%2); + --ch; + --ca; + --pos; + } +} + +void Q3TextDocument::setStyleSheet(Q3StyleSheet *s) +{ + if (!s) + return; + sheet_ = s; + list_tm = list_bm = par_tm = par_bm = 12; + list_lm = 40; + li_tm = li_bm = 0; + Q3StyleSheetItem* item = s->item(QLatin1String("ol")); + if (item) { + list_tm = qMax(0,item->margin(Q3StyleSheetItem::MarginTop)); + list_bm = qMax(0,item->margin(Q3StyleSheetItem::MarginBottom)); + list_lm = qMax(0,item->margin(Q3StyleSheetItem::MarginLeft)); + } + if ((item = s->item(QLatin1String("li")))) { + li_tm = qMax(0,item->margin(Q3StyleSheetItem::MarginTop)); + li_bm = qMax(0,item->margin(Q3StyleSheetItem::MarginBottom)); + } + if ((item = s->item(QLatin1String("p")))) { + par_tm = qMax(0,item->margin(Q3StyleSheetItem::MarginTop)); + par_bm = qMax(0,item->margin(Q3StyleSheetItem::MarginBottom)); + } +} + +void Q3TextDocument::setUnderlineLinks(bool b) { + underlLinks = b; + for (int idx = 0; idx < childList.size(); ++idx) { + Q3TextDocument *dc = childList.at(idx); + dc->setUnderlineLinks(b); + } +} + +void Q3TextStringChar::setFormat(Q3TextFormat *f) +{ + if (type == Regular) { + p.format = f; + } else { +#ifndef QT_NO_TEXTCUSTOMITEM + if (!p.custom) { + p.custom = new CustomData; + p.custom->custom = 0; + } + p.custom->format = f; +#endif + } +} + +#ifndef QT_NO_TEXTCUSTOMITEM +void Q3TextStringChar::setCustomItem(Q3TextCustomItem *i) +{ + if (type == Regular) { + Q3TextFormat *f = format(); + p.custom = new CustomData; + p.custom->format = f; + } else { + delete p.custom->custom; + } + p.custom->custom = i; + type = (type == Anchor ? CustomAnchor : Custom); +} + +void Q3TextStringChar::loseCustomItem() +{ + if (type == Custom) { + Q3TextFormat *f = p.custom->format; + p.custom->custom = 0; + delete p.custom; + type = Regular; + p.format = f; + } else if (type == CustomAnchor) { + p.custom->custom = 0; + type = Anchor; + } +} + +#endif + +QString Q3TextStringChar::anchorName() const +{ + if (type == Regular) + return QString(); + else + return p.custom->anchorName; +} + +QString Q3TextStringChar::anchorHref() const +{ + if (type == Regular) + return QString(); + else + return p.custom->anchorHref; +} + +void Q3TextStringChar::setAnchor(const QString& name, const QString& href) +{ + if (type == Regular) { + Q3TextFormat *f = format(); + p.custom = new CustomData; +#ifndef QT_NO_TEXTCUSTOMITEM + p.custom->custom = 0; +#endif + p.custom->format = f; + type = Anchor; + } else if (type == Custom) { + type = CustomAnchor; + } + p.custom->anchorName = name; + p.custom->anchorHref = href; +} + + +int Q3TextString::width(int idx) const +{ + int w = 0; + Q3TextStringChar *c = &at(idx); + if (!c->charStop || c->c.unicode() == 0xad || c->c.unicode() == 0x2028) + return 0; +#ifndef QT_NO_TEXTCUSTOMITEM + if(c->isCustom()) { + if(c->customItem()->placement() == Q3TextCustomItem::PlaceInline) + w = c->customItem()->width; + } else +#endif + { + int r = c->c.row(); + if(r < 0x06 +#ifndef Q_WS_WIN + // Uniscribe's handling of Asian makes the condition below fail. + || (r > 0x1f && !(r > 0xd7 && r < 0xe0)) +#endif + ) { + w = c->format()->width(c->c); + } else { + // complex text. We need some hacks to get the right metric here + w = c->format()->width(toString(), idx); + } + } + return w; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Q3TextParagraph::Q3TextParagraph(Q3TextDocument *dc, Q3TextParagraph *pr, Q3TextParagraph *nx, bool updateIds) + : p(pr), n(nx), docOrPseudo(dc), + changed(false), firstFormat(true), firstPProcess(true), needPreProcess(false), fullWidth(true), + lastInFrame(false), visible(true), breakable(true), movedDown(false), + mightHaveCustomItems(false), hasdoc(dc != 0), litem(false), rtext(false), + align(0), lstyle(Q3StyleSheetItem::ListDisc), invalid(0), mSelections(0), +#ifndef QT_NO_TEXTCUSTOMITEM + mFloatingItems(0), +#endif + utm(0), ubm(0), ulm(0), urm(0), uflm(0), ulinespacing(0), + tabStopWidth(0), minwidth(0), tArray(0), eData(0), ldepth(0) +{ + lstyle = Q3StyleSheetItem::ListDisc; + if (!hasdoc) + docOrPseudo = new Q3TextParagraphPseudoDocument; + bgcol = 0; + list_val = -1; + paintdevice = 0; + Q3TextFormat* defFormat = formatCollection()->defaultFormat(); + if (!hasdoc) { + tabStopWidth = defFormat->width(QLatin1Char('x')) * 8; + pseudoDocument()->commandHistory = new Q3TextCommandHistory(100); + } + + if (p) + p->n = this; + if (n) + n->p = this; + + if (!p && hasdoc) + document()->setFirstParagraph(this); + if (!n && hasdoc) + document()->setLastParagraph(this); + + state = -1; + + if (p) + id = p->id + 1; + else + id = 0; + if (n && updateIds) { + Q3TextParagraph *s = n; + while (s) { + s->id = s->p->id + 1; + s->invalidateStyleCache(); + s = s->n; + } + } + + str = new Q3TextString(); + const QChar ch(QLatin1Char(' ')); + str->insert(0, &ch, 1, formatCollection()->defaultFormat()); +} + +Q3TextParagraph::~Q3TextParagraph() +{ + delete str; + if (hasdoc) { + register Q3TextDocument *doc = document(); + if (this == doc->minwParag) { + doc->minwParag = 0; + doc->minw = 0; + } + if (this == doc->curParag) + doc->curParag = 0; + } else { + delete pseudoDocument(); + } + delete [] tArray; + delete eData; + QMap<int, QTextLineStart*>::Iterator it = lineStarts.begin(); + for (; it != lineStarts.end(); ++it) + delete *it; + if (mSelections) + delete mSelections; +#ifndef QT_NO_TEXTCUSTOMITEM + if (mFloatingItems) + delete mFloatingItems; +#endif + if (p) + p->setNext(n); + if (n) + n->setPrev(p); + delete bgcol; +} + +void Q3TextParagraph::setNext(Q3TextParagraph *s) +{ + n = s; + if (!n && hasdoc) + document()->setLastParagraph(this); +} + +void Q3TextParagraph::setPrev(Q3TextParagraph *s) +{ + p = s; + if (!p && hasdoc) + document()->setFirstParagraph(this); +} + +void Q3TextParagraph::invalidate(int chr) +{ + if (invalid < 0) + invalid = chr; + else + invalid = qMin(invalid, chr); +#ifndef QT_NO_TEXTCUSTOMITEM + if (mFloatingItems) { + for (int idx = 0; idx < mFloatingItems->size(); ++idx) { + Q3TextCustomItem *i = mFloatingItems->at(idx); + i->ypos = -1; + } + } +#endif + invalidateStyleCache(); +} + +void Q3TextParagraph::invalidateStyleCache() +{ + if (list_val < 0) + list_val = -1; +} + + +void Q3TextParagraph::insert(int index, const QString &s) +{ + insert(index, s.unicode(), s.length()); +} + +void Q3TextParagraph::insert(int index, const QChar *unicode, int len) +{ + if (hasdoc && !document()->useFormatCollection() && document()->preProcessor()) + str->insert(index, unicode, len, + document()->preProcessor()->format(Q3TextPreProcessor::Standard)); + else + str->insert(index, unicode, len, formatCollection()->defaultFormat()); + invalidate(index); + needPreProcess = true; +} + +void Q3TextParagraph::truncate(int index) +{ + str->truncate(index); + insert(length(), QLatin1String(" ")); + needPreProcess = true; +} + +void Q3TextParagraph::remove(int index, int len) +{ + if (index + len - str->length() > 0) + return; +#ifndef QT_NO_TEXTCUSTOMITEM + for (int i = index; i < index + len; ++i) { + Q3TextStringChar *c = at(i); + if (hasdoc && c->isCustom()) { + document()->unregisterCustomItem(c->customItem(), this); + } + } +#endif + str->remove(index, len); + invalidate(0); + needPreProcess = true; +} + +void Q3TextParagraph::join(Q3TextParagraph *s) +{ + int oh = r.height() + s->r.height(); + n = s->n; + if (n) + n->p = this; + else if (hasdoc) + document()->setLastParagraph(this); + + int start = str->length(); + if (length() > 0 && at(length() - 1)->c == QLatin1Char(' ')) { + remove(length() - 1, 1); + --start; + } + append(s->str->toString(), true); + + for (int i = 0; i < s->length(); ++i) { + if (!hasdoc || document()->useFormatCollection()) { + s->str->at(i).format()->addRef(); + str->setFormat(i + start, s->str->at(i).format(), true); + } +#ifndef QT_NO_TEXTCUSTOMITEM + if (s->str->at(i).isCustom()) { + Q3TextCustomItem * item = s->str->at(i).customItem(); + str->at(i + start).setCustomItem(item); + s->str->at(i).loseCustomItem(); + if (hasdoc) { + document()->unregisterCustomItem(item, s); + document()->registerCustomItem(item, this); + } + } + if (s->str->at(i).isAnchor()) { + str->at(i + start).setAnchor(s->str->at(i).anchorName(), + s->str->at(i).anchorHref()); + } +#endif + } + + if (!extraData() && s->extraData()) { + setExtraData(s->extraData()); + s->setExtraData(0); + } else if (extraData() && s->extraData()) { + extraData()->join(s->extraData()); + } + delete s; + invalidate(0); + r.setHeight(oh); + needPreProcess = true; + if (n) { + Q3TextParagraph *s = n; + s->invalidate(0); + while (s) { + s->id = s->p->id + 1; + s->state = -1; + s->needPreProcess = true; + s->changed = true; + s->invalidateStyleCache(); + s = s->n; + } + } + format(); + state = -1; +} + +void Q3TextParagraph::move(int &dy) +{ + if (dy == 0) + return; + changed = true; + r.moveBy(0, dy); +#ifndef QT_NO_TEXTCUSTOMITEM + if (mFloatingItems) { + for (int idx = 0; idx < mFloatingItems->size(); ++idx) { + Q3TextCustomItem *i = mFloatingItems->at(idx); + i->ypos += dy; + } + } +#endif + if (p) + p->lastInFrame = true; + + // do page breaks if required + if (hasdoc && document()->isPageBreakEnabled()) { + int shift; + if ((shift = document()->formatter()->formatVertically( document(), this))) { + if (p) + p->setChanged(true); + dy += shift; + } + } +} + +void Q3TextParagraph::format(int start, bool doMove) +{ + if (!str || str->length() == 0 || !formatter()) + return; + + if (hasdoc && + document()->preProcessor() && + (needPreProcess || state == -1)) + document()->preProcessor()->process(document(), this, invalid <= 0 ? 0 : invalid); + needPreProcess = false; + + if (invalid == -1) + return; + + r.moveTopLeft(QPoint(documentX(), p ? p->r.y() + p->r.height() : documentY())); + if (p) + p->lastInFrame = false; + + movedDown = false; + bool formattedAgain = false; + + formatAgain: + + r.setWidth(documentWidth()); +#ifndef QT_NO_TEXTCUSTOMITEM + if (hasdoc && mFloatingItems) { + for (int idx = 0; idx < mFloatingItems->size(); ++idx) { + Q3TextCustomItem *i = mFloatingItems->at(idx); + i->ypos = r.y(); + if (i->placement() == Q3TextCustomItem::PlaceRight) { + i->xpos = r.x() + r.width() - i->width; + } + } + } +#endif + QMap<int, QTextLineStart*> oldLineStarts = lineStarts; + lineStarts.clear(); + int y = formatter()->format(document(), this, start, oldLineStarts); + + + r.setWidth(qMax(r.width(), formatter()->minimumWidth())); + + + QMap<int, QTextLineStart*>::Iterator it = oldLineStarts.begin(); + + for (; it != oldLineStarts.end(); ++it) + delete *it; + + if (!hasdoc) { // qt_format_text bounding rect handling + it = lineStarts.begin(); + int usedw = 0; + for (; it != lineStarts.end(); ++it) + usedw = qMax(usedw, (*it)->w); + if (r.width() <= 0) { + // if the user specifies an invalid rect, this means that the + // bounding box should grow to the width that the text actually + // needs + r.setWidth(usedw); + } else { + r.setWidth(qMin(usedw, r.width())); + } + } + + if (y != r.height()) + r.setHeight(y); + + if (!visible) { + r.setHeight(0); + } else { + int minw = minwidth = formatter()->minimumWidth(); + int wused = formatter()->widthUsed(); + wused = qMax(minw, wused); + if (hasdoc) { + document()->setMinimumWidth(minw, wused, this); + } else { + pseudoDocument()->minw = qMax(pseudoDocument()->minw, minw); + pseudoDocument()->wused = qMax(pseudoDocument()->wused, wused); + } + } + + // do page breaks if required + if (hasdoc && document()->isPageBreakEnabled()) { + int shift = document()->formatter()->formatVertically(document(), this); + if (shift && !formattedAgain) { + formattedAgain = true; + goto formatAgain; + } + } + + if (n && doMove && n->invalid == -1 && r.y() + r.height() != n->r.y()) { + int dy = (r.y() + r.height()) - n->r.y(); + Q3TextParagraph *s = n; + bool makeInvalid = p && p->lastInFrame; + while (s && dy) { + if (!s->isFullWidth()) + makeInvalid = true; + if (makeInvalid) + s->invalidate(0); + s->move(dy); + if (s->lastInFrame) + makeInvalid = true; + s = s->n; + } + } + + firstFormat = false; + changed = true; + invalid = -1; + //##### string()->setTextChanged(false); +} + +int Q3TextParagraph::lineHeightOfChar(int i, int *bl, int *y) const +{ + if (!isValid()) + ((Q3TextParagraph*)this)->format(); + + QMap<int, QTextLineStart*>::ConstIterator it = lineStarts.end(); + --it; + for (;;) { + if (i >= it.key()) { + if (bl) + *bl = (*it)->baseLine; + if (y) + *y = (*it)->y; + return (*it)->h; + } + if (it == lineStarts.begin()) + break; + --it; + } + + qWarning("Q3TextParagraph::lineHeightOfChar: couldn't find lh for %d", i); + return 15; +} + +Q3TextStringChar *Q3TextParagraph::lineStartOfChar(int i, int *index, int *line) const +{ + if (!isValid()) + ((Q3TextParagraph*)this)->format(); + + int l = (int)lineStarts.count() - 1; + QMap<int, QTextLineStart*>::ConstIterator it = lineStarts.end(); + --it; + for (;;) { + if (i >= it.key()) { + if (index) + *index = it.key(); + if (line) + *line = l; + return &str->at(it.key()); + } + if (it == lineStarts.begin()) + break; + --it; + --l; + } + + qWarning("Q3TextParagraph::lineStartOfChar: couldn't find %d", i); + return 0; +} + +int Q3TextParagraph::lines() const +{ + if (!isValid()) + ((Q3TextParagraph*)this)->format(); + + return (int)lineStarts.count(); +} + +Q3TextStringChar *Q3TextParagraph::lineStartOfLine(int line, int *index) const +{ + if (!isValid()) + ((Q3TextParagraph*)this)->format(); + + if (line >= 0 && line < (int)lineStarts.count()) { + QMap<int, QTextLineStart*>::ConstIterator it = lineStarts.begin(); + while (line-- > 0) + ++it; + int i = it.key(); + if (index) + *index = i; + return &str->at(i); + } + + qWarning("Q3TextParagraph::lineStartOfLine: couldn't find %d", line); + return 0; +} + +int Q3TextParagraph::leftGap() const +{ + if (!isValid()) + ((Q3TextParagraph*)this)->format(); + + if (str->length() == 0) + return 0; + + int line = 0; + int x = str->length() ? str->at(0).x : 0; /* set x to x of first char */ + if (str->isBidi()) { + for (int i = 1; i < str->length()-1; ++i) + x = qMin(x, str->at(i).x); + return x; + } + + QMap<int, QTextLineStart*>::ConstIterator it = lineStarts.begin(); + while (line < (int)lineStarts.count()) { + int i = it.key(); /* char index */ + x = qMin(x, str->at(i).x); + ++it; + ++line; + } + return x; +} + +void Q3TextParagraph::setFormat(int index, int len, Q3TextFormat *f, bool useCollection, int flags) +{ + if (!f) + return; + if (index < 0) + index = 0; + if (index > str->length() - 1) + index = str->length() - 1; + if (index + len >= str->length()) + len = str->length() - index; + + Q3TextFormatCollection *fc = 0; + if (useCollection) + fc = formatCollection(); + Q3TextFormat *of; + for (int i = 0; i < len; ++i) { + of = str->at(i + index).format(); + if (!changed && (!of || f->key() != of->key())) + changed = true; + if (invalid == -1 && + (f->font().family() != of->font().family() || + f->font().pointSize() != of->font().pointSize() || + f->font().weight() != of->font().weight() || + f->font().italic() != of->font().italic() || + f->vAlign() != of->vAlign())) { + invalidate(0); + } + if (flags == -1 || flags == Q3TextFormat::Format || !fc) { + if (fc) + f = fc->format(f); + str->setFormat(i + index, f, useCollection); + } else { + Q3TextFormat *fm = fc->format(of, f, flags); + str->setFormat(i + index, fm, useCollection); + } + } +} + +void Q3TextParagraph::indent(int *oldIndent, int *newIndent) +{ + if (!hasdoc || !document()->indent() || isListItem()) { + if (oldIndent) + *oldIndent = 0; + if (newIndent) + *newIndent = 0; + if (oldIndent && newIndent) + *newIndent = *oldIndent; + return; + } + document()->indent()->indent(document(), this, oldIndent, newIndent); +} + +void Q3TextParagraph::paint(QPainter &painter, const QPalette &pal, Q3TextCursor *cursor, + bool drawSelections, int clipx, int clipy, int clipw, int cliph) +{ + if (!visible) + return; + int i, y, h, baseLine, xstart, xend = 0; + i = y =h = baseLine = 0; + QRect cursorRect; + drawSelections &= (mSelections != 0); + // macintosh full-width selection style + bool fullWidthStyle = QApplication::style()->styleHint(QStyle::SH_RichText_FullWidthSelection); + int fullSelectionWidth = 0; + if (drawSelections && fullWidthStyle) + fullSelectionWidth = (hasdoc ? document()->width() : r.width()); + + QString qstr = str->toString(); + qstr.detach(); + // ### workaround so that \n are not drawn, actually this should + // be fixed in QFont somewhere (under Windows you get ugly boxes + // otherwise) + QChar* uc = (QChar*) qstr.unicode(); + for (int ii = 0; ii < qstr.length(); ii++) + if (uc[(int)ii]== QLatin1Char(QLatin1Char('\n')) || uc[(int)ii] == QLatin1Char('\t')) + uc[(int)ii] = 0x20; + + int line = -1; + int paintStart = 0; + Q3TextStringChar *chr = 0; + Q3TextStringChar *nextchr = at(0); + for (i = 0; i < length(); i++) { + chr = nextchr; + if (i < length()-1) + nextchr = at(i+1); + + // we flush at end of document + bool flush = (i == length()-1); + bool ignoreSoftHyphen = false; + if (!flush) { + // we flush at end of line + flush |= nextchr->lineStart; + // we flush on format changes + flush |= (nextchr->format() != chr->format()); + // we flush on link changes + flush |= (nextchr->isLink() != chr->isLink()); + // we flush on start of run + flush |= (nextchr->bidiLevel != chr->bidiLevel); + // we flush on bidi changes + flush |= (nextchr->rightToLeft != chr->rightToLeft); + // we flush before and after tabs + flush |= (chr->c == QLatin1Char('\t') || nextchr->c == QLatin1Char('\t')); + // we flush on soft hyphens + if (chr->c.unicode() == 0xad) { + flush = true; + if (!nextchr->lineStart) + ignoreSoftHyphen = true; + } + // we flush on custom items + flush |= chr->isCustom(); + // we flush before custom items + flush |= nextchr->isCustom(); + // when painting justified, we flush on spaces + if ((alignment() & Qt::AlignJustify) == Qt::AlignJustify) + flush |= chr->whiteSpace; + } + + // init a new line + if (chr->lineStart) { + ++line; + paintStart = i; + lineInfo(line, y, h, baseLine); + if (clipy != -1 && cliph != 0 && y + r.y() - h > clipy + cliph) { // outside clip area, leave + break; + } + + // if this is the first line and we are a list item, draw the the bullet label + if (line == 0 && isListItem()) { + int x = chr->x; + if (str->isBidi()) { + if (str->isRightToLeft()) { + x = chr->x + str->width(0); + for (int k = 1; k < length(); ++k) { + if (str->at(k).lineStart) + break; + x = qMax(x, str->at(k).x + str->width(k)); + } + } else { + x = chr->x; + for (int k = 1; k < length(); ++k) { + if (str->at(k).lineStart) + break; + x = qMin(x, str->at(k).x); + } + } + } + drawLabel(&painter, x, y, 0, 0, baseLine, pal); + } + } + + // check for cursor mark + if (cursor && this == cursor->paragraph() && i == cursor->index()) { + Q3TextStringChar *c = i == 0 ? chr : chr - 1; + cursorRect.setRect(cursor->x() , y + baseLine - c->format()->ascent(), + 1, c->format()->height()); + } + + if (flush) { // something changed, draw what we have so far + if (chr->rightToLeft) { + xstart = chr->x; + xend = at(paintStart)->x + str->width(paintStart); + } else { + xstart = at(paintStart)->x; + xend = chr->x; + if (i < length() - 1) { + if (!str->at(i + 1).lineStart && + str->at(i + 1).rightToLeft == chr->rightToLeft) + xend = str->at(i + 1).x; + else + xend += str->width(i); + } + } + + if ((clipx == -1 || clipw <= 0 || (xend >= clipx && xstart <= clipx + clipw)) && + (clipy == -1 || clipy < y+r.y()+h)) { + if (!chr->isCustom()) + drawString(painter, qstr, paintStart, i - paintStart + (ignoreSoftHyphen ? 0 : 1), xstart, y, + baseLine, xend-xstart, h, drawSelections, fullSelectionWidth, + chr, pal, chr->rightToLeft); +#ifndef QT_NO_TEXTCUSTOMITEM + else if (chr->customItem()->placement() == Q3TextCustomItem::PlaceInline) { + bool inSelection = false; + if (drawSelections) { + QMap<int, Q3TextParagraphSelection>::ConstIterator it = mSelections->constFind(Q3TextDocument::Standard); + inSelection = (it != mSelections->constEnd() && (*it).start <= i && (*it).end > i); + } + chr->customItem()->draw(&painter, chr->x, y, + clipx == -1 ? clipx : (clipx - r.x()), + clipy == -1 ? clipy : (clipy - r.y()), + clipw, cliph, pal, inSelection); + } +#endif + } + paintStart = i+1; + } + + } + + // time to draw the cursor + const int cursor_extent = 4; + if (!cursorRect.isNull() && cursor && + ((clipx == -1 || clipw == -1) || (cursorRect.right()+cursor_extent >= clipx && cursorRect.left()-cursor_extent <= clipx + clipw))) { + painter.fillRect(cursorRect, pal.color(QPalette::Text)); + painter.save(); + if (string()->isBidi()) { + if (at(cursor->index())->rightToLeft) { + painter.setPen(Qt::black); + painter.drawLine(cursorRect.x(), cursorRect.y(), cursorRect.x() - cursor_extent / 2, cursorRect.y() + cursor_extent / 2); + painter.drawLine(cursorRect.x(), cursorRect.y() + cursor_extent, cursorRect.x() - cursor_extent / 2, cursorRect.y() + cursor_extent / 2); + } else { + painter.setPen(Qt::black); + painter.drawLine(cursorRect.x(), cursorRect.y(), cursorRect.x() + cursor_extent / 2, cursorRect.y() + cursor_extent / 2); + painter.drawLine(cursorRect.x(), cursorRect.y() + cursor_extent, cursorRect.x() + cursor_extent / 2, cursorRect.y() + cursor_extent / 2); + } + } + painter.restore(); + } +} + +//#define BIDI_DEBUG + +void Q3TextParagraph::setColorForSelection(QColor &color, QPainter &painter, + const QPalette &pal, int selection) +{ + if (selection < 0) + return; + color = (hasdoc && selection != Q3TextDocument::Standard) ? + document()->selectionColor(selection) : + pal.color(QPalette::Highlight); + QColor text = (hasdoc && document()->hasSelectionTextColor(selection)) ? document()->selectionTextColor(selection) : pal.color(QPalette::HighlightedText); + if (text.isValid()) + painter.setPen(text); +} + +void Q3TextParagraph::drawString(QPainter &painter, const QString &str, int start, int len, + int xstart, int y, int baseLine, int w, int h, + bool drawSelections, int fullSelectionWidth, + Q3TextStringChar *formatChar, const QPalette& pal, + bool rightToLeft) +{ + bool plainText = hasdoc ? document()->textFormat() == Qt::PlainText : false; + Q3TextFormat* format = formatChar->format(); + + int textFlags = int(rightToLeft ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight); + + if (!plainText || (hasdoc && format->color() != document()->formatCollection()->defaultFormat()->color())) + painter.setPen(QPen(format->color())); + else + painter.setPen(pal.text().color()); + painter.setFont(format->font()); + + if (hasdoc && formatChar->isAnchor() && !formatChar->anchorHref().isEmpty()) { + if (format->useLinkColor()) + painter.setPen(document()->linkColor.isValid() ? document()->linkColor : + pal.link().color()); + if (document()->underlineLinks()) { + QFont fn = format->font(); + fn.setUnderline(true); + painter.setFont(fn); + } + } + + int real_length = len; + if (len && !rightToLeft && start + len == length()) // don't draw the last character (trailing space) + len--; + if (len && str.unicode()[start+len-1] == QChar::LineSeparator) + len--; + + + Q3TextFormat::VerticalAlignment vAlign = format->vAlign(); + if (vAlign != Q3TextFormat::AlignNormal) { + // sub or superscript + QFont f(painter.font()); + if (format->fontSizesInPixels()) + f.setPixelSize((f.pixelSize() * 2) / 3); + else + f.setPointSize((f.pointSize() * 2) / 3); + painter.setFont(f); + int h = painter.fontMetrics().height(); + baseLine += (vAlign == Q3TextFormat::AlignSubScript) ? h/6 : -h/2; + } + + bool allSelected = false; + if (drawSelections) { + QMap<int, Q3TextParagraphSelection>::ConstIterator it = mSelections->constFind(Q3TextDocument::Standard); + allSelected = (it != mSelections->constEnd() && (*it).start <= start && (*it).end >= start+len); + } + if (!allSelected) + painter.drawText(QPointF(xstart, y + baseLine), str.mid(start, len), textFlags, /*justificationPadding*/0); + +#ifdef BIDI_DEBUG + painter.save(); + painter.setPen (Qt::red); + painter.drawLine(xstart, y, xstart, y + baseLine); + painter.drawLine(xstart, y + baseLine/2, xstart + 10, y + baseLine/2); + int w = 0; + int i = 0; + while(i < len) + w += painter.fontMetrics().charWidth(str, start + i++); + painter.setPen (Qt::blue); + painter.drawLine(xstart + w - 1, y, xstart + w - 1, y + baseLine); + painter.drawLine(xstart + w - 1, y + baseLine/2, xstart + w - 1 - 10, y + baseLine/2); + painter.restore(); +#endif + + // check if we are in a selection and draw it + if (drawSelections) { + QMap<int, Q3TextParagraphSelection>::ConstIterator it = mSelections->constEnd(); + while (it != mSelections->constBegin()) { + --it; + int selStart = (*it).start; + int selEnd = (*it).end; + int tmpw = w; + + selStart = qMax(selStart, start); + int real_selEnd = qMin(selEnd, start+real_length); + selEnd = qMin(selEnd, start+len); + bool extendRight = false; + bool extendLeft = false; + bool selWrap = (real_selEnd == length()-1 && n && n->hasSelection(it.key())); + if (selWrap || this->str->at(real_selEnd).lineStart) { + extendRight = (fullSelectionWidth != 0); + if (!extendRight && !rightToLeft) + tmpw += painter.fontMetrics().width(QLatin1Char(' ')); + } + if (fullSelectionWidth && (selStart == 0 || this->str->at(selStart).lineStart)) { + extendLeft = true; + } + if (this->str->isRightToLeft() != rightToLeft) + extendLeft = extendRight = false; + + if (this->str->isRightToLeft()) { + bool tmp = extendLeft; + extendLeft = extendRight; + extendRight = tmp; + } + + if (selStart < real_selEnd || + (selWrap && fullSelectionWidth && extendRight && + // don't draw the standard selection on a printer= + (it.key() != Q3TextDocument::Standard || !is_printer(&painter)))) { + int selection = it.key(); + QColor color; + setColorForSelection(color, painter, pal, selection); + if (selStart != start || selEnd != start + len || selWrap) { + // have to clip + painter.save(); + int cs, ce; + if (rightToLeft) { + cs = (selEnd != start + len) ? + this->str->at(this->str->previousCursorPosition(selEnd)).x : xstart; + ce = (selStart != start) ? + this->str->at(this->str->previousCursorPosition(selStart)).x : xstart+tmpw; + } else { + cs = (selStart != start) ? this->str->at(selStart).x : xstart; + ce = (selEnd != start + len) ? this->str->at(selEnd).x : xstart+tmpw; + } + QRect r(cs, y, ce-cs, h); + if (extendLeft) + r.setLeft(0); + if (extendRight) + r.setRight(fullSelectionWidth); + QRegion reg(r); + if (painter.hasClipping()) + reg &= painter.clipRegion(); + painter.setClipRegion(reg); + } + int xleft = xstart; + if (extendLeft) { + tmpw += xstart; + xleft = 0; + } + if (extendRight) + tmpw = fullSelectionWidth - xleft; + if(color.isValid()) + painter.fillRect(xleft, y, tmpw, h, color); + painter.drawText(QPointF(xstart, y + baseLine), str.mid(start, len), textFlags, /*justificationPadding*/0); + if (selStart != start || selEnd != start + len || selWrap) + painter.restore(); + } + } + } + + if (format->isMisspelled()) { + painter.save(); + painter.setPen(QPen(Qt::red, 1, Qt::DotLine)); + painter.drawLine(xstart, y + baseLine + 1, xstart + w, y + baseLine + 1); + painter.restore(); + } + + if (hasdoc && formatChar->isAnchor() && !formatChar->anchorHref().isEmpty() && + document()->focusIndicator.parag == this && + ((document()->focusIndicator.start >= start && + document()->focusIndicator.start + document()->focusIndicator.len <= start + len) + || (document()->focusIndicator.start <= start && + document()->focusIndicator.start + document()->focusIndicator.len >= start + len))) { + QStyleOptionFocusRect opt; + opt.rect.setRect(xstart, y, w, h); +#ifndef Q_WS_WIN + opt.state = QStyle::State_None; +#else + // force drawing a focus rect but only on windows because it's + // configurable by the user in windows settings (see + // SH_UnderlineShortcut style hint) and we want to override + // this settings. + opt.state = QStyle::State_KeyboardFocusChange; +#endif + opt.palette = pal; + QApplication::style()->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, &painter); + } +} + +void Q3TextParagraph::drawLabel(QPainter* p, int x, int y, int w, int h, int base, + const QPalette& pal) +{ + QRect r (x, y, w, h); + Q3StyleSheetItem::ListStyle s = listStyle(); + + p->save(); + Q3TextFormat *format = at(0)->format(); + if (format) { + p->setPen(format->color()); + p->setFont(format->font()); + } + QFontMetrics fm(p->fontMetrics()); + int size = fm.lineSpacing() / 3; + + bool rtl = str->isRightToLeft(); + + switch (s) { + case Q3StyleSheetItem::ListDecimal: + case Q3StyleSheetItem::ListLowerAlpha: + case Q3StyleSheetItem::ListUpperAlpha: + { + if (list_val == -1) { // uninitialised list value, calcluate the right one + int depth = listDepth(); + list_val--; + // ### evil, square and expensive. This needs to be done when formatting, not when painting + Q3TextParagraph* s = prev(); + int depth_s; + while (s && (depth_s = s->listDepth()) >= depth) { + if (depth_s == depth && s->isListItem()) + list_val--; + s = s->prev(); + } + } + + int n = list_val; + if (n < -1) + n = -n - 1; + QString l; + switch (s) { + case Q3StyleSheetItem::ListLowerAlpha: + if (n < 27) { + l = QLatin1Char(('a' + (char) (n-1))); + break; + } + case Q3StyleSheetItem::ListUpperAlpha: + if (n < 27) { + l = QLatin1Char(('A' + (char) (n-1))); + break; + } + break; + default: //Q3StyleSheetItem::ListDecimal: + l.setNum(n); + break; + } + if (rtl) + l.prepend(QLatin1String(" .")); + else + l += QString::fromLatin1(". "); + int x = (rtl ? r.left() : r.right() - fm.width(l)); + p->drawText(x, r.top() + base, l); + } + break; + case Q3StyleSheetItem::ListSquare: + { + int x = rtl ? r.left() + size : r.right() - size*2; + QRect er(x, r.top() + fm.height() / 2 - size / 2, size, size); + p->fillRect(er , pal.brush(QPalette::Text)); + } + break; + case Q3StyleSheetItem::ListCircle: + { + int x = rtl ? r.left() + size : r.right() - size*2; + QRect er(x, r.top() + fm.height() / 2 - size / 2, size, size); + p->drawEllipse(er); + } + break; + case Q3StyleSheetItem::ListDisc: + default: + { + p->setBrush(pal.brush(QPalette::Text)); + int x = rtl ? r.left() + size : r.right() - size*2; + QRect er(x, r.top() + fm.height() / 2 - size / 2, size, size); + p->drawEllipse(er); + p->setBrush(Qt::NoBrush); + } + break; + } + + p->restore(); +} + +#ifndef QT_NO_DATASTREAM +void Q3TextParagraph::readStyleInformation(QDataStream &stream) +{ + int int_align, int_lstyle; + uchar uchar_litem, uchar_rtext, uchar_dir; + stream >> int_align >> int_lstyle >> utm >> ubm >> ulm >> urm >> uflm + >> ulinespacing >> ldepth >> uchar_litem >> uchar_rtext >> uchar_dir; + align = int_align; lstyle = (Q3StyleSheetItem::ListStyle) int_lstyle; + litem = uchar_litem; rtext = uchar_rtext; str->setDirection((QChar::Direction)uchar_dir); + Q3TextParagraph* s = prev() ? prev() : this; + while (s) { + s->invalidate(0); + s = s->next(); + } +} + +void Q3TextParagraph::writeStyleInformation(QDataStream& stream) const +{ + stream << (int) align << (int) lstyle << utm << ubm << ulm << urm << uflm << ulinespacing << ldepth << (uchar)litem << (uchar)rtext << (uchar)str->direction(); +} +#endif + + +void Q3TextParagraph::setListItem(bool li) +{ + if ((bool)litem == li) + return; + litem = li; + changed = true; + Q3TextParagraph* s = prev() ? prev() : this; + while (s) { + s->invalidate(0); + s = s->next(); + } +} + +void Q3TextParagraph::setListDepth(int depth) { + if (!hasdoc || depth == ldepth) + return; + ldepth = depth; + Q3TextParagraph* s = prev() ? prev() : this; + while (s) { + s->invalidate(0); + s = s->next(); + } +} + +int *Q3TextParagraph::tabArray() const +{ + int *ta = tArray; + if (!ta && hasdoc) + ta = document()->tabArray(); + return ta; +} + +int Q3TextParagraph::nextTab(int, int x) +{ + int *ta = tArray; + if (hasdoc) { + if (!ta) + ta = document()->tabArray(); + tabStopWidth = document()->tabStopWidth(); + } + if (ta) { + int i = 0; + while (ta[i]) { + if (ta[i] >= x) + return tArray[i]; + ++i; + } + return tArray[0]; + } else { + int n; + if (tabStopWidth != 0) + n = x / tabStopWidth; + else + return x; + return tabStopWidth * (n + 1); + } +} + +void Q3TextParagraph::adjustToPainter(QPainter *p) +{ +#ifndef QT_NO_TEXTCUSTOMITEM + for (int i = 0; i < length(); ++i) { + if (at(i)->isCustom()) + at(i)->customItem()->adjustToPainter(p); + } +#endif +} + +Q3TextFormatCollection *Q3TextParagraph::formatCollection() const +{ + if (hasdoc) + return document()->formatCollection(); + Q3TextFormatCollection* fc = &pseudoDocument()->collection; + if (paintdevice != fc->paintDevice()) + fc->setPaintDevice(paintdevice); + return fc; +} + +QString Q3TextParagraph::richText() const +{ + QString s; + Q3TextStringChar *formatChar = 0; + QString spaces; + bool doStart = richTextExportStart && richTextExportStart->paragraph() == this; + bool doEnd = richTextExportEnd && richTextExportEnd->paragraph() == this; + int i; + QString lastAnchorName; + for (i = 0; i < length()-1; ++i) { + if (doStart && i && richTextExportStart->index() == i) + s += QLatin1String("<!--StartFragment-->"); + if (doEnd && richTextExportEnd->index() == i) + s += QLatin1String("<!--EndFragment-->"); + Q3TextStringChar *c = &str->at(i); + if (c->isAnchor() && !c->anchorName().isEmpty() && c->anchorName() != lastAnchorName) { + lastAnchorName = c->anchorName(); + if (c->anchorName().contains(QLatin1Char('#'))) { + QStringList l = c->anchorName().split(QLatin1Char('#')); + for (QStringList::ConstIterator it = l.constBegin(); it != l.constEnd(); ++it) + s += QLatin1String("<a name=\"") + *it + QLatin1String("\"></a>"); + } else { + s += QLatin1String("<a name=\"") + c->anchorName() + QLatin1String("\"></a>"); + } + } + if (!formatChar) { + s += c->format()->makeFormatChangeTags(formatCollection()->defaultFormat(), + 0, QString(), c->anchorHref()); + formatChar = c; + } else if ((formatChar->format()->key() != c->format()->key()) || + (c->anchorHref() != formatChar->anchorHref())) { + s += c->format()->makeFormatChangeTags(formatCollection()->defaultFormat(), + formatChar->format() , formatChar->anchorHref(), c->anchorHref()); + formatChar = c; + } + if (c->c == QLatin1Char('<')) + s += QLatin1String("<"); + else if (c->c == QLatin1Char('>')) + s += QLatin1String(">"); + else if (c->c == QLatin1Char('&')) + s += QLatin1String("&"); + else if (c->c == QLatin1Char('\"')) + s += QLatin1String("""); +#ifndef QT_NO_TEXTCUSTOMITEM + else if (c->isCustom()) + s += c->customItem()->richText(); +#endif + else if (c->c == QLatin1Char('\n') || c->c == QChar::LineSeparator) + s += QLatin1String("<br />"); // space on purpose for compatibility with Netscape, Lynx & Co. + else + s += c->c; + } + if (doEnd && richTextExportEnd->index() == i) + s += QLatin1String("<!--EndFragment-->"); + if (formatChar) + s += formatChar->format()->makeFormatEndTags(formatCollection()->defaultFormat(), formatChar->anchorHref()); + return s; +} + +void Q3TextParagraph::addCommand(Q3TextCommand *cmd) +{ + if (!hasdoc) + pseudoDocument()->commandHistory->addCommand(cmd); + else + document()->commands()->addCommand(cmd); +} + +Q3TextCursor *Q3TextParagraph::undo(Q3TextCursor *c) +{ + if (!hasdoc) + return pseudoDocument()->commandHistory->undo(c); + return document()->commands()->undo(c); +} + +Q3TextCursor *Q3TextParagraph::redo(Q3TextCursor *c) +{ + if (!hasdoc) + return pseudoDocument()->commandHistory->redo(c); + return document()->commands()->redo(c); +} + +int Q3TextParagraph::topMargin() const +{ + int m = 0; + if (rtext) { + m = isListItem() ? (document()->li_tm/qMax(1,listDepth()*listDepth())) : + (listDepth() ? 0 : document()->par_tm); + if (listDepth() == 1 &&( !prev() || prev()->listDepth() < listDepth())) + m = qMax<int>(m, document()->list_tm); + } + m += utm; + return scale(m, Q3TextFormat::painter()); +} + +int Q3TextParagraph::bottomMargin() const +{ + int m = 0; + if (rtext) { + m = isListItem() ? (document()->li_bm/qMax(1,listDepth()*listDepth())) : + (listDepth() ? 0 : document()->par_bm); + if (listDepth() == 1 &&( !next() || next()->listDepth() < listDepth())) + m = qMax<int>(m, document()->list_bm); + } + m += ubm; + return scale(m, Q3TextFormat::painter()); +} + +int Q3TextParagraph::leftMargin() const +{ + int m = ulm; + if (listDepth() && !string()->isRightToLeft()) + m += listDepth() * document()->list_lm; + return scale(m, Q3TextFormat::painter()); +} + +int Q3TextParagraph::firstLineMargin() const +{ + int m = uflm; + return scale(m, Q3TextFormat::painter()); +} + +int Q3TextParagraph::rightMargin() const +{ + int m = urm; + if (listDepth() && string()->isRightToLeft()) + m += listDepth() * document()->list_lm; + return scale(m, Q3TextFormat::painter()); +} + +int Q3TextParagraph::lineSpacing() const +{ + int l = ulinespacing; + l = scale(l, Q3TextFormat::painter()); + return l; +} + +void Q3TextParagraph::copyParagData(Q3TextParagraph *parag) +{ + rtext = parag->rtext; + lstyle = parag->lstyle; + ldepth = parag->ldepth; + litem = parag->litem; + align = parag->align; + utm = parag->utm; + ubm = parag->ubm; + urm = parag->urm; + ulm = parag->ulm; + uflm = parag->uflm; + ulinespacing = parag->ulinespacing; + QColor *c = parag->backgroundColor(); + if (c) + setBackgroundColor(*c); + str->setDirection(parag->str->direction()); +} + +void Q3TextParagraph::show() +{ + if (visible || !hasdoc) + return; + visible = true; +} + +void Q3TextParagraph::hide() +{ + if (!visible || !hasdoc) + return; + visible = false; +} + +void Q3TextParagraph::setDirection(QChar::Direction dir) +{ + if (str && str->direction() != dir) { + str->setDirection(dir); + invalidate(0); + } +} + +QChar::Direction Q3TextParagraph::direction() const +{ + return (str ? str->direction() : QChar::DirON); +} + +void Q3TextParagraph::setChanged(bool b, bool recursive) +{ + changed = b; + if (recursive) { + if (document() && document()->parentParagraph()) + document()->parentParagraph()->setChanged(b, recursive); + } +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +Q3TextPreProcessor::Q3TextPreProcessor() +{ +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Q3TextFormatter::Q3TextFormatter() + : thisminw(0), thiswused(0), wrapEnabled(true), wrapColumn(-1), biw(false) +{ +} + +QTextLineStart *Q3TextFormatter::formatLine(Q3TextParagraph *parag, Q3TextString *string, QTextLineStart *line, + Q3TextStringChar *startChar, Q3TextStringChar *lastChar, int align, int space) +{ + if (lastChar < startChar) + return new QTextLineStart; +#ifndef QT_NO_COMPLEXTEXT + if(string->isBidi()) + return bidiReorderLine(parag, string, line, startChar, lastChar, align, space); +#endif + int start = (startChar - &string->at(0)); + int last = (lastChar - &string->at(0)); + + // ignore white space at the end of the line. + Q3TextStringChar *ch = lastChar; + while (ch > startChar && ch->whiteSpace) { + space += ch->format()->width(QLatin1Char(' ')); + --ch; + } + + if (space < 0) + space = 0; + + // do alignment Auto == Left in this case + if (align & Qt::AlignHCenter || align & Qt::AlignRight) { + if (align & Qt::AlignHCenter) + space /= 2; + for (int j = start; j <= last; ++j) + string->at(j).x += space; + } else if (align & Qt::AlignJustify) { + int numSpaces = 0; + // End at "last-1", the last space ends up with a width of 0 + for (int j = last-1; j >= start; --j) { + // Start at last tab, if any. + Q3TextStringChar &ch = string->at(j); + if (ch.c == QLatin1Char('\t')) { + start = j+1; + break; + } + if(ch.whiteSpace) + numSpaces++; + } + int toAdd = 0; + for (int k = start + 1; k <= last; ++k) { + Q3TextStringChar &ch = string->at(k); + if(numSpaces && ch.whiteSpace) { + int s = space / numSpaces; + toAdd += s; + space -= s; + numSpaces--; + } + string->at(k).x += toAdd; + } + } + + if (last >= 0 && last < string->length()) + line->w = string->at(last).x + string->width(last); + else + line->w = 0; + + return new QTextLineStart; +} + +#ifndef QT_NO_COMPLEXTEXT + +#ifdef BIDI_DEBUG +QT_BEGIN_INCLUDE_NAMESPACE +#include <iostream> +QT_END_INCLUDE_NAMESPACE +#endif + +// collects one line of the paragraph and transforms it to visual order +QTextLineStart *Q3TextFormatter::bidiReorderLine(Q3TextParagraph * /*parag*/, Q3TextString *text, QTextLineStart *line, + Q3TextStringChar *startChar, Q3TextStringChar *lastChar, int align, int space) +{ + // ignore white space at the end of the line. + int endSpaces = 0; + while (lastChar > startChar && lastChar->whiteSpace) { + space += lastChar->format()->width(QLatin1Char(' ')); + --lastChar; + ++endSpaces; + } + + int start = (startChar - &text->at(0)); + int last = (lastChar - &text->at(0)); + + int length = lastChar - startChar + 1; + + + int x = startChar->x; + + unsigned char _levels[256]; + int _visual[256]; + + unsigned char *levels = _levels; + int *visual = _visual; + + if (length > 255) { + levels = (unsigned char *)malloc(length*sizeof(unsigned char)); + visual = (int *)malloc(length*sizeof(int)); + } + + //qDebug("bidiReorderLine: length=%d (%d-%d)", length, start, last); + + Q3TextStringChar *ch = startChar; + unsigned char *l = levels; + while (ch <= lastChar) { + //qDebug(" level: %d", ch->bidiLevel); + *(l++) = (ch++)->bidiLevel; + } + + QTextEngine::bidiReorder(length, levels, visual); + + // now construct the reordered string out of the runs... + + int numSpaces = 0; + align = QStyle::visualAlignment(text->isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight, QFlag(align)); + + // This is not really correct, but as we can't make the scroll bar move to the left of the origin, + // this ensures all text can be scrolled to and read. + if (space < 0) + space = 0; + + if (align & Qt::AlignHCenter) + x += space/2; + else if (align & Qt::AlignRight) + x += space; + else if (align & Qt::AlignJustify) { + // End at "last-1", the last space ends up with a width of 0 + for (int j = last-1; j >= start; --j) { + // Start at last tab, if any. + Q3TextStringChar &ch = text->at(j); + if (ch.c == QLatin1Char('\t')) { + start = j+1; + break; + } + if(ch.whiteSpace) + numSpaces++; + } + } + + int toAdd = 0; + int xorig = x; + Q3TextStringChar *lc = startChar + visual[0]; + for (int i = 0; i < length; i++) { + Q3TextStringChar *ch = startChar + visual[i]; + if (numSpaces && ch->whiteSpace) { + int s = space / numSpaces; + toAdd += s; + space -= s; + numSpaces--; + } + + if (lc->format() != ch->format() && !ch->c.isSpace() + && lc->format()->font().italic() && !ch->format()->font().italic()) { + int rb = lc->format()->fontMetrics().rightBearing(lc->c); + if (rb < 0) + x -= rb; + } + + ch->x = x + toAdd; + ch->rightToLeft = ch->bidiLevel % 2; + //qDebug("visual: %d (%p) placed at %d rightToLeft=%d", visual[i], ch, x +toAdd, ch->rightToLeft ); + int ww = 0; + if (ch->c.unicode() >= 32 || ch->c == QLatin1Char(QLatin1Char('\t')) || ch->c == QLatin1Char('\n') || ch->isCustom()) { + ww = text->width(start+visual[i]); + } else { + ww = ch->format()->width(QLatin1Char(' ')); + } + x += ww; + lc = ch; + } + x += toAdd; + + while (endSpaces--) { + ++lastChar; + int sw = lastChar->format()->width(QLatin1Char(' ')); + if (text->isRightToLeft()) { + xorig -= sw; + lastChar->x = xorig; + ch->rightToLeft = true; + } else { + lastChar->x = x; + x += sw; + ch->rightToLeft = false; + } + } + + line->w = x; + + if (length > 255) { + free(levels); + free(visual); + } + + return new QTextLineStart; +} +#endif + + +void Q3TextFormatter::insertLineStart(Q3TextParagraph *parag, int index, QTextLineStart *ls) +{ + QMap<int, QTextLineStart*>::Iterator it; + if ((it = parag->lineStartList().find(index)) == parag->lineStartList().end()) { + parag->lineStartList().insert(index, ls); + } else { + delete *it; + parag->lineStartList().erase(it); + parag->lineStartList().insert(index, ls); + } +} + + +/* Standard pagebreak algorithm using Q3TextFlow::adjustFlow. Returns + the shift of the paragraphs bottom line. + */ +int Q3TextFormatter::formatVertically(Q3TextDocument* doc, Q3TextParagraph* parag) +{ + int oldHeight = parag->rect().height(); + QMap<int, QTextLineStart*>& lineStarts = parag->lineStartList(); + QMap<int, QTextLineStart*>::Iterator it = lineStarts.begin(); + int h = parag->prev() ? qMax(parag->prev()->bottomMargin(),parag->topMargin()) / 2: 0; + for (; it != lineStarts.end() ; ++it ) { + QTextLineStart * ls = it.value(); + ls->y = h; + Q3TextStringChar *c = ¶g->string()->at(it.key()); +#ifndef QT_NO_TEXTCUSTOMITEM + if (c && c->customItem() && c->customItem()->ownLine()) { + int h = c->customItem()->height; + c->customItem()->pageBreak(parag->rect().y() + ls->y + ls->baseLine - h, doc->flow()); + int delta = c->customItem()->height - h; + ls->h += delta; + if (delta) + parag->setMovedDown(true); + } else +#endif + { + + int shift = doc->flow()->adjustFlow(parag->rect().y() + ls->y, ls->w, ls->h); + ls->y += shift; + if (shift) + parag->setMovedDown(true); + } + h = ls->y + ls->h; + } + int m = parag->bottomMargin(); + if (!parag->next()) + m = 0; + else + m = qMax(m, parag->next()->topMargin()) / 2; + h += m; + parag->setHeight(h); + return h - oldHeight; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Q3TextFormatterBreakInWords::Q3TextFormatterBreakInWords() +{ +} + +#define SPACE(s) s + +int Q3TextFormatterBreakInWords::format(Q3TextDocument *doc,Q3TextParagraph *parag, + int start, const QMap<int, QTextLineStart*> &) +{ + // make sure bidi information is correct. + (void)parag->string()->isBidi(); + + Q3TextStringChar *c = 0; + Q3TextStringChar *firstChar = 0; + int left = doc ? parag->leftMargin() + doc->leftMargin() : 0; + int x = left + (doc ? parag->firstLineMargin() : 0); + int dw = parag->documentVisibleWidth() - (doc ? doc->rightMargin() : 0); + int y = parag->prev() ? qMax(parag->prev()->bottomMargin(),parag->topMargin()) / 2: 0; + int h = y; + int len = parag->length(); + if (doc) + x = doc->flow()->adjustLMargin(y + parag->rect().y(), parag->rect().height(), x, 4); + int rm = parag->rightMargin(); + int w = dw - (doc ? doc->flow()->adjustRMargin(y + parag->rect().y(), parag->rect().height(), rm, 4) : 0); + bool fullWidth = true; + int minw = 0; + int wused = 0; + bool wrapEnabled = isWrapEnabled(parag); + + start = 0; //######### what is the point with start?! (Matthias) + if (start == 0) + c = ¶g->string()->at(0); + + int i = start; + QTextLineStart *lineStart = new QTextLineStart(y, y, 0); + insertLineStart(parag, 0, lineStart); + + QPainter *painter = Q3TextFormat::painter(); + + int col = 0; + int ww = 0; + QChar lastChr; + int tabBase = left < x ? left : x; + for (; i < len; ++i, ++col) { + if (c) + lastChr = c->c; + c = ¶g->string()->at(i); + // ### the lines below should not be needed + if (painter) + c->format()->setPainter(painter); + if (i > 0) { + c->lineStart = 0; + } else { + c->lineStart = 1; + firstChar = c; + } + if (c->c.unicode() >= 32 || c->isCustom()) { + ww = parag->string()->width(i); + } else if (c->c == QLatin1Char('\t')) { + int nx = parag->nextTab(i, x - tabBase) + tabBase; + if (nx < x) + ww = w - x; + else + ww = nx - x; + } else { + ww = c->format()->width(QLatin1Char(' ')); + } + +#ifndef QT_NO_TEXTCUSTOMITEM + if (c->isCustom() && c->customItem()->ownLine()) { + x = doc ? doc->flow()->adjustLMargin(y + parag->rect().y(), parag->rect().height(), left, 4) : left; + w = dw - (doc ? doc->flow()->adjustRMargin(y + parag->rect().y(), parag->rect().height(), rm, 4) : 0); + c->customItem()->resize(w - x); + w = dw; + y += h; + h = c->height(); + lineStart = new QTextLineStart(y, h, h); + insertLineStart(parag, i, lineStart); + c->lineStart = 1; + firstChar = c; + x = 0xffffff; + continue; + } +#endif + + if (wrapEnabled && + ((wrapAtColumn() == -1 && x + ww > w) || + (wrapAtColumn() != -1 && col >= wrapAtColumn()))) { + x = doc ? parag->document()->flow()->adjustLMargin(y + parag->rect().y(), parag->rect().height(), left, 4) : left; + w = dw; + y += h; + h = c->height(); + lineStart = formatLine(parag, parag->string(), lineStart, firstChar, c-1); + lineStart->y = y; + insertLineStart(parag, i, lineStart); + lineStart->baseLine = c->ascent(); + lineStart->h = c->height(); + c->lineStart = 1; + firstChar = c; + col = 0; + if (wrapAtColumn() != -1) + minw = qMax(minw, w); + } else if (lineStart) { + lineStart->baseLine = qMax(lineStart->baseLine, c->ascent()); + h = qMax(h, c->height()); + lineStart->h = h; + } + + c->x = x; + x += ww; + wused = qMax(wused, x); + } + + int m = parag->bottomMargin(); + if (!parag->next()) + m = 0; + else + m = qMax(m, parag->next()->topMargin()) / 2; + parag->setFullWidth(fullWidth); + y += h + m; + if (doc) + minw += doc->rightMargin(); + if (!wrapEnabled) + minw = qMax(minw, wused); + + thisminw = minw; + thiswused = wused; + return y; +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Q3TextFormatterBreakWords::Q3TextFormatterBreakWords() +{ +} + +#define DO_FLOW(lineStart) do{ if (doc && doc->isPageBreakEnabled()) { \ + int yflow = lineStart->y + parag->rect().y();\ + int shift = doc->flow()->adjustFlow(yflow, dw, lineStart->h); \ + lineStart->y += shift;\ + y += shift;\ + }}while(false) + +int Q3TextFormatterBreakWords::format(Q3TextDocument *doc, Q3TextParagraph *parag, + int start, const QMap<int, QTextLineStart*> &) +{ + // make sure bidi information is correct. + (void)parag->string()->isBidi(); + + Q3TextStringChar *c = 0; + Q3TextStringChar *firstChar = 0; + Q3TextString *string = parag->string(); + int left = doc ? parag->leftMargin() + doc->leftMargin() : 0; + int x = left + (doc ? parag->firstLineMargin() : 0); + int y = parag->prev() ? qMax(parag->prev()->bottomMargin(),parag->topMargin()) / 2: 0; + int h = y; + int len = parag->length(); + if (doc) + x = doc->flow()->adjustLMargin(y + parag->rect().y(), parag->rect().height(), x, 0); + int dw = parag->documentVisibleWidth() - (doc ? (left != x ? 0 : doc->rightMargin()) : 0); + + int curLeft = x; + int rm = parag->rightMargin(); + int rdiff = doc ? doc->flow()->adjustRMargin(y + parag->rect().y(), parag->rect().height(), rm, 0) : 0; + int w = dw - rdiff; + bool fullWidth = true; + int marg = left + rdiff; + int minw = 0; + int wused = 0; + int tminw = marg; + int linespacing = doc ? parag->lineSpacing() : 0; + bool wrapEnabled = isWrapEnabled(parag); + + start = 0; + + int i = start; + QTextLineStart *lineStart = new QTextLineStart(y, y, 0); + insertLineStart(parag, 0, lineStart); + int lastBreak = -1; + int tmpBaseLine = 0, tmph = 0; + bool lastWasNonInlineCustom = false; + + int align = parag->alignment(); + if (align == Qt::AlignAuto && doc && doc->alignment() != Qt::AlignAuto) + align = doc->alignment(); + + align &= Qt::AlignHorizontal_Mask; + + // ### hack. The last char in the paragraph is always invisible, + // ### and somehow sometimes has a wrong format. It changes + // ### between // layouting and printing. This corrects some + // ### layouting errors in BiDi mode due to this. + if (len > 1) { + c = ¶g->string()->at(len - 1); + if (!c->isAnchor()) { + if (c->format()) + c->format()->removeRef(); + c->setFormat(string->at(len - 2).format()); + if (c->format()) + c->format()->addRef(); + } + } + + c = ¶g->string()->at(0); + + QPainter *painter = Q3TextFormat::painter(); + int col = 0; + int ww = 0; + QChar lastChr = c->c; + Q3TextFormat *lastFormat = c->format(); + int tabBase = left < x ? left : x; + for (; i < len; ++i, ++col) { + if (i) { + c = ¶g->string()->at(i-1); + lastChr = c->c; + lastFormat = c->format(); + } + bool lastWasOwnLineCustomItem = lastBreak == -2; + bool hadBreakableChar = lastBreak != -1; + bool lastWasHardBreak = lastChr == QChar::LineSeparator; + + // ### next line should not be needed + if (painter) + c->format()->setPainter(painter); + c = &string->at(i); + + if (lastFormat != c->format() && !c->c.isSpace() + && lastFormat->font().italic() && !c->format()->font().italic()) { + int rb = lastFormat->fontMetrics().rightBearing(lastChr); + if (rb < 0) + x -= rb; + } + + if ((i > 0 && (x > curLeft || ww == 0)) || lastWasNonInlineCustom) { + c->lineStart = 0; + } else { + c->lineStart = 1; + firstChar = c; + } + + // ignore non spacing marks for column count. + if (col != 0 && QChar::category(c->c.unicode()) == QChar::Mark_NonSpacing) + --col; + +#ifndef QT_NO_TEXTCUSTOMITEM + lastWasNonInlineCustom = (c->isCustom() && c->customItem()->placement() != Q3TextCustomItem::PlaceInline); +#endif + + if (c->c.unicode() >= 32 || c->isCustom()) { + ww = string->width(i); + } else if (c->c == QLatin1Char('\t')) { + if (align == Qt::AlignRight || align == Qt::AlignCenter) { + // we can not (yet) do tabs + ww = c->format()->width(QLatin1Char(' ')); + } else { + int tabx = lastWasHardBreak ? (left + (doc ? parag->firstLineMargin() : 0)) : x; + int nx = parag->nextTab(i, tabx - tabBase) + tabBase; + if (nx < tabx) // strrrange... + ww = 0; + else + ww = nx - tabx; + } + } else { + ww = c->format()->width(QLatin1Char(' ')); + } + +#ifndef QT_NO_TEXTCUSTOMITEM + Q3TextCustomItem* ci = c->customItem(); + if (c->isCustom() && ci->ownLine()) { + QTextLineStart *lineStart2 = formatLine(parag, string, lineStart, firstChar, c-1, align, SPACE(w - x - ww)); + x = doc ? doc->flow()->adjustLMargin(y + parag->rect().y(), parag->rect().height(), left, 4) : left; + w = dw - (doc ? doc->flow()->adjustRMargin(y + parag->rect().y(), parag->rect().height(), rm, 4) : 0); + ci->resize(w - x); + if (ci->width < w - x) { + if (align & Qt::AlignHCenter) + x = (w - ci->width) / 2; + else if (align & Qt::AlignRight) { + x = w - ci->width; + } + } + c->x = x; + curLeft = x; + if (i == 0 || !isBreakable(string, i-1) || + string->at(i - 1).lineStart == 0) { + y += qMax(h, qMax(tmph, linespacing)); + tmph = c->height(); + h = tmph; + lineStart = lineStart2; + lineStart->y = y; + insertLineStart(parag, i, lineStart); + c->lineStart = 1; + firstChar = c; + } else { + tmph = c->height(); + h = tmph; + delete lineStart2; + } + lineStart->h = h; + lineStart->baseLine = h; + tmpBaseLine = lineStart->baseLine; + lastBreak = -2; + x = w; + minw = qMax(minw, tminw); + + int tw = ci->minimumWidth() + (doc ? doc->leftMargin() : 0); + if (tw < QWIDGETSIZE_MAX) + tminw = tw; + else + tminw = marg; + wused = qMax(wused, ci->width); + continue; + } else if (c->isCustom() && ci->placement() != Q3TextCustomItem::PlaceInline) { + int tw = ci->minimumWidth(); + if (tw < QWIDGETSIZE_MAX) + minw = qMax(minw, tw); + } +#endif + // we break if + // 1. the last character was a hard break (QChar::LineSeparator) or + // 2. the last character was a own-line custom item (eg. table or ruler) or + // 3. wrapping was enabled, it was not a space and following + // condition is true: We either had a breakable character + // previously or we ar allowed to break in words and - either + // we break at w pixels and the current char would exceed that + // or - we break at a column and the current character would + // exceed that. + if (lastWasHardBreak || lastWasOwnLineCustomItem || + (wrapEnabled && + ((!c->c.isSpace() && (hadBreakableChar || allowBreakInWords()) && + ((wrapAtColumn() == -1 && x + ww > w) || + (wrapAtColumn() != -1 && col >= wrapAtColumn())))) + ) + ) { + if (wrapAtColumn() != -1) + minw = qMax(minw, x + ww); + // if a break was forced (no breakable char, hard break or own line custom item), break immediately.... + if (!hadBreakableChar || lastWasHardBreak || lastWasOwnLineCustomItem) { + if (lineStart) { + lineStart->baseLine = qMax(lineStart->baseLine, tmpBaseLine); + h = qMax(h, tmph); + lineStart->h = h; + DO_FLOW(lineStart); + } + lineStart = formatLine(parag, string, lineStart, firstChar, c-1, align, SPACE(w - x)); + x = doc ? doc->flow()->adjustLMargin(y + parag->rect().y(), parag->rect().height(), left, 4) : left; + w = dw - (doc ? doc->flow()->adjustRMargin(y + parag->rect().y(), parag->rect().height(), rm, 4) : 0); + if (!doc && c->c == QLatin1Char('\t')) { // qt_format_text tab handling + int nx = parag->nextTab(i, x - tabBase) + tabBase; + if (nx < x) + ww = w - x; + else + ww = nx - x; + } + curLeft = x; + y += qMax(h, linespacing); + tmph = c->height(); + h = 0; + lineStart->y = y; + insertLineStart(parag, i, lineStart); + lineStart->baseLine = c->ascent(); + lineStart->h = c->height(); + c->lineStart = 1; + firstChar = c; + tmpBaseLine = lineStart->baseLine; + lastBreak = -1; + col = 0; + if (allowBreakInWords() || lastWasHardBreak) { + minw = qMax(minw, tminw); + tminw = marg + ww; + } + } else { // ... otherwise if we had a breakable char, break there + DO_FLOW(lineStart); + c->x = x; + i = lastBreak; + lineStart = formatLine(parag, string, lineStart, firstChar, parag->at(lastBreak),align, SPACE(w - string->at(i+1).x)); + x = doc ? doc->flow()->adjustLMargin(y + parag->rect().y(), parag->rect().height(), left, 4) : left; + w = dw - (doc ? doc->flow()->adjustRMargin(y + parag->rect().y(), parag->rect().height(), rm, 4) : 0); + if (!doc && c->c == QLatin1Char('\t')) { // qt_format_text tab handling + int nx = parag->nextTab(i, x - tabBase) + tabBase; + if (nx < x) + ww = w - x; + else + ww = nx - x; + } + curLeft = x; + y += qMax(h, linespacing); + tmph = c->height(); + h = tmph; + lineStart->y = y; + insertLineStart(parag, i + 1, lineStart); + lineStart->baseLine = c->ascent(); + lineStart->h = c->height(); + c->lineStart = 1; + firstChar = c; + tmpBaseLine = lineStart->baseLine; + lastBreak = -1; + col = 0; + minw = qMax(minw, tminw); + tminw = marg; + continue; + } + } else if (lineStart && isBreakable(string, i)) { + if (len <= 2 || i < len - 1) { + tmpBaseLine = qMax(tmpBaseLine, c->ascent()); + tmph = qMax(tmph, c->height()); + } + minw = qMax(minw, tminw); + + tminw = marg + ww; + lineStart->baseLine = qMax(lineStart->baseLine, tmpBaseLine); + h = qMax(h, tmph); + lineStart->h = h; + if (i < len - 2 || c->c != QLatin1Char(' ')) + lastBreak = i; + } else { + tminw += ww; + int cascent = c->ascent(); + int cheight = c->height(); + int belowBaseLine = qMax(tmph - tmpBaseLine, cheight-cascent); + tmpBaseLine = qMax(tmpBaseLine, cascent); + tmph = tmpBaseLine + belowBaseLine; + } + + c->x = x; + x += ww; + wused = qMax(wused, x); + } + + if (lineStart) { + lineStart->baseLine = qMax(lineStart->baseLine, tmpBaseLine); + h = qMax(h, tmph); + lineStart->h = h; + // last line in a paragraph is not justified + if (align & Qt::AlignJustify) { + align |= Qt::AlignLeft; + align &= ~(Qt::AlignJustify|Qt::AlignAbsolute); + } + DO_FLOW(lineStart); + lineStart = formatLine(parag, string, lineStart, firstChar, c, align, SPACE(w - x)); + delete lineStart; + } + + minw = qMax(minw, tminw); + if (doc) + minw += doc->rightMargin(); + + int m = parag->bottomMargin(); + if (!parag->next()) + m = 0; + else + m = qMax(m, parag->next()->topMargin()) / 2; + parag->setFullWidth(fullWidth); + y += qMax(h, linespacing) + m; + + wused += rm; + if (!wrapEnabled || wrapAtColumn() != -1) + minw = qMax(minw, wused); + + // This is the case where we are breaking wherever we darn well please + // in cases like that, the minw should not be the length of the entire + // word, because we necessarily want to show the word on the whole line. + // example: word wrap in iconview + if (allowBreakInWords() && minw > wused) + minw = wused; + + thisminw = minw; + thiswused = wused; + return y; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Q3TextIndent::Q3TextIndent() +{ +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Q3TextFormatCollection::Q3TextFormatCollection() + : paintdevice(0) +{ + defFormat = new Q3TextFormat(QApplication::font(), + QApplication::palette().color(QPalette::Active, QPalette::Text)); + lastFormat = cres = 0; + cflags = -1; + cachedFormat = 0; +} + +Q3TextFormatCollection::~Q3TextFormatCollection() +{ + QHash<QString, Q3TextFormat *>::ConstIterator it = cKey.constBegin(); + while (it != cKey.constEnd()) { + delete it.value(); + ++it; + } + delete defFormat; +} + +void Q3TextFormatCollection::setPaintDevice(QPaintDevice *pd) +{ + paintdevice = pd; + +#if defined(Q_WS_X11) + int scr = (paintdevice) ? paintdevice->x11Screen() : QX11Info::appScreen(); + + defFormat->fn.x11SetScreen(scr); + defFormat->update(); + + QHash<QString, Q3TextFormat *>::Iterator it = cKey.begin(); + for (; it != cKey.end(); ++it) { + Q3TextFormat *format = *it; + format->fn.x11SetScreen(scr); + format->update(); + } +#endif // Q_WS_X11 +} + +Q3TextFormat *Q3TextFormatCollection::format(Q3TextFormat *f) +{ + if (f->parent() == this || f == defFormat) { + lastFormat = f; + lastFormat->addRef(); + return lastFormat; + } + + if (f == lastFormat || (lastFormat && f->key() == lastFormat->key())) { + lastFormat->addRef(); + return lastFormat; + } + + Q3TextFormat *fm = cKey.value(f->key()); + if (fm) { + lastFormat = fm; + lastFormat->addRef(); + return lastFormat; + } + + if (f->key() == defFormat->key()) + return defFormat; + + lastFormat = createFormat(*f); + lastFormat->collection = this; + cKey.insert(lastFormat->key(), lastFormat); + return lastFormat; +} + +Q3TextFormat *Q3TextFormatCollection::format(Q3TextFormat *of, Q3TextFormat *nf, int flags) +{ + if (cres && kof == of->key() && knf == nf->key() && cflags == flags) { + cres->addRef(); + return cres; + } + + cres = createFormat(*of); + kof = of->key(); + knf = nf->key(); + cflags = flags; + if (flags & Q3TextFormat::Bold) + cres->fn.setBold(nf->fn.bold()); + if (flags & Q3TextFormat::Italic) + cres->fn.setItalic(nf->fn.italic()); + if (flags & Q3TextFormat::Underline) + cres->fn.setUnderline(nf->fn.underline()); + if (flags & Q3TextFormat::StrikeOut) + cres->fn.setStrikeOut(nf->fn.strikeOut()); + if (flags & Q3TextFormat::Family) + cres->fn.setFamily(nf->fn.family()); + if (flags & Q3TextFormat::Size) { + if (of->usePixelSizes) + cres->fn.setPixelSize(nf->fn.pixelSize()); + else + cres->fn.setPointSize(nf->fn.pointSize()); + } + if (flags & Q3TextFormat::Color) + cres->col = nf->col; + if (flags & Q3TextFormat::Misspelled) + cres->missp = nf->missp; + if (flags & Q3TextFormat::VAlign) + cres->ha = nf->ha; + cres->update(); + + Q3TextFormat *fm = cKey.value(cres->key()); + if (!fm) { + cres->collection = this; + cKey.insert(cres->key(), cres); + } else { + delete cres; + cres = fm; + cres->addRef(); + } + + return cres; +} + +Q3TextFormat *Q3TextFormatCollection::format(const QFont &f, const QColor &c) +{ + if (cachedFormat && cfont == f && ccol == c) { + cachedFormat->addRef(); + return cachedFormat; + } + + QString key = Q3TextFormat::getKey(f, c, false, Q3TextFormat::AlignNormal); + cachedFormat = cKey.value(key); + cfont = f; + ccol = c; + + if (cachedFormat) { + cachedFormat->addRef(); + return cachedFormat; + } + + if (key == defFormat->key()) + return defFormat; + + cachedFormat = createFormat(f, c); + cachedFormat->collection = this; + cKey.insert(cachedFormat->key(), cachedFormat); + if (cachedFormat->key() != key) + qWarning("ASSERT: keys for format not identical: '%s '%s'", cachedFormat->key().latin1(), key.latin1()); + return cachedFormat; +} + +void Q3TextFormatCollection::remove(Q3TextFormat *f) +{ + if (lastFormat == f) + lastFormat = 0; + if (cres == f) + cres = 0; + if (cachedFormat == f) + cachedFormat = 0; + if (cKey.value(f->key()) == f) + delete cKey.take(f->key()); +} + +#define UPDATE(up, lo, rest) \ + if (font.lo##rest() != defFormat->fn.lo##rest() && fm->fn.lo##rest() == defFormat->fn.lo##rest()) \ + fm->fn.set##up##rest(font.lo##rest()) + +void Q3TextFormatCollection::updateDefaultFormat(const QFont &font, const QColor &color, Q3StyleSheet *sheet) +{ + bool usePixels = font.pointSize() == -1; + bool changeSize = usePixels ? font.pixelSize() != defFormat->fn.pixelSize() : + font.pointSize() != defFormat->fn.pointSize(); + int base = usePixels ? font.pixelSize() : font.pointSize(); + QHash<QString, Q3TextFormat *>::Iterator it = cKey.begin(); + for (; it != cKey.end(); ++it) { + Q3TextFormat *fm = *it; + UPDATE(F, f, amily); + UPDATE(W, w, eight); + UPDATE(B, b, old); + UPDATE(I, i, talic); + UPDATE(U, u, nderline); + if (changeSize) { + fm->stdSize = base; + fm->usePixelSizes = usePixels; + if (usePixels) + fm->fn.setPixelSize(fm->stdSize); + else + fm->fn.setPointSize(fm->stdSize); + sheet->scaleFont(fm->fn, fm->logicalFontSize); + } + if (color.isValid() && color != defFormat->col && fm->col == defFormat->col) + fm->col = color; + fm->update(); + } + + defFormat->fn = font; + defFormat->col = color; + defFormat->update(); + defFormat->stdSize = base; + defFormat->usePixelSizes = usePixels; + + updateKeys(); +} + +// the keys in cKey have changed, rebuild the hashtable +void Q3TextFormatCollection::updateKeys() +{ + if (cKey.isEmpty()) + return; + Q3TextFormat** formats = new Q3TextFormat *[cKey.count() + 1]; + Q3TextFormat **f = formats; + for (QHash<QString, Q3TextFormat *>::Iterator it = cKey.begin(); it != cKey.end(); ++it, ++f) + *f = *it; + *f = 0; + cKey.clear(); + for (f = formats; *f; f++) + cKey.insert((*f)->key(), *f); + delete [] formats; +} + + + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +void Q3TextFormat::setBold(bool b) +{ + if (b == fn.bold()) + return; + fn.setBold(b); + update(); +} + +void Q3TextFormat::setMisspelled(bool b) +{ + if (b == (bool)missp) + return; + missp = b; + update(); +} + +void Q3TextFormat::setVAlign(VerticalAlignment a) +{ + if (a == ha) + return; + ha = a; + update(); +} + +void Q3TextFormat::setItalic(bool b) +{ + if (b == fn.italic()) + return; + fn.setItalic(b); + update(); +} + +void Q3TextFormat::setUnderline(bool b) +{ + if (b == fn.underline()) + return; + fn.setUnderline(b); + update(); +} + +void Q3TextFormat::setStrikeOut(bool b) +{ + if (b == fn.strikeOut()) + return; + fn.setStrikeOut(b); + update(); +} + +void Q3TextFormat::setFamily(const QString &f) +{ + if (f == fn.family()) + return; + fn.setFamily(f); + update(); +} + +void Q3TextFormat::setPointSize(int s) +{ + if (s == fn.pointSize()) + return; + fn.setPointSize(s); + usePixelSizes = false; + update(); +} + +void Q3TextFormat::setFont(const QFont &f) +{ + if (f == fn && !k.isEmpty()) + return; + fn = f; + update(); +} + +void Q3TextFormat::setColor(const QColor &c) +{ + if (c == col) + return; + col = c; + update(); +} + +QString Q3TextFormat::makeFormatChangeTags(Q3TextFormat* defaultFormat, Q3TextFormat *f, + const QString& oldAnchorHref, const QString& anchorHref ) const +{ + QString tag; + if (f) + tag += f->makeFormatEndTags(defaultFormat, oldAnchorHref); + + if (!anchorHref.isEmpty()) + tag += QLatin1String("<a href=\"") + anchorHref + QLatin1String("\">"); + + if (font() != defaultFormat->font() + || vAlign() != defaultFormat->vAlign() + || color().rgb() != defaultFormat->color().rgb()) { + QString s; + if (font().family() != defaultFormat->font().family()) + s += QString(s.size()?QLatin1String(";"):QLatin1String("")) + QLatin1String("font-family:") + fn.family(); + if (font().italic() && font().italic() != defaultFormat->font().italic()) + s += QString(s.size()?QLatin1String(";"):QLatin1String("")) + QLatin1String("font-style:") + (font().italic() ? QLatin1String("italic") : QLatin1String("normal")); + if (font().pointSize() != defaultFormat->font().pointSize()) + s += QString(s.size()?QLatin1String(";"):QLatin1String("")) + QLatin1String("font-size:") + QString::number(fn.pointSize()) + QLatin1String("pt"); + if (font().weight() != defaultFormat->font().weight()) + s += QString(s.size()?QLatin1String(";"):QLatin1String("")) + QLatin1String("font-weight:") + QString::number(fn.weight() * 8); + QString textDecoration; + bool none = false; + if ( font().underline() != defaultFormat->font().underline() ) { + if (font().underline()) + textDecoration = QLatin1String("underline"); + else + none = true; + } + if ( font().overline() != defaultFormat->font().overline() ) { + if (font().overline()) + textDecoration += QLatin1String(" overline"); + else + none = true; + } + if ( font().strikeOut() != defaultFormat->font().strikeOut() ) { + if (font().strikeOut()) + textDecoration += QLatin1String(" line-through"); + else + none = true; + } + if (none && textDecoration.isEmpty()) + textDecoration = QLatin1String("none"); + if (!textDecoration.isEmpty()) + s += QString(s.size()?QLatin1String(";"):QLatin1String("")) + QLatin1String("text-decoration:") + textDecoration; + if (vAlign() != defaultFormat->vAlign()) { + s += QString(s.size()?QLatin1String(";"):QLatin1String("")) + QLatin1String("vertical-align:"); + if (vAlign() == Q3TextFormat::AlignSuperScript) + s += QLatin1String("super"); + else if (vAlign() == Q3TextFormat::AlignSubScript) + s += QLatin1String("sub"); + else + s += QLatin1String("normal"); + } + if (color().rgb() != defaultFormat->color().rgb()) + s += QString(s.size()?QLatin1String(";"):QLatin1String("")) + QLatin1String("color:") + col.name(); + if (!s.isEmpty()) + tag += QLatin1String("<span style=\"") + s + QLatin1String("\">"); + } + + return tag; +} + +QString Q3TextFormat::makeFormatEndTags(Q3TextFormat* defaultFormat, const QString& anchorHref) const +{ + QString tag; + if (font().family() != defaultFormat->font().family() + || font().pointSize() != defaultFormat->font().pointSize() + || font().weight() != defaultFormat->font().weight() + || font().italic() != defaultFormat->font().italic() + || font().underline() != defaultFormat->font().underline() + || font().strikeOut() != defaultFormat->font().strikeOut() + || vAlign() != defaultFormat->vAlign() + || color().rgb() != defaultFormat->color().rgb()) + tag += QLatin1String("</span>"); + if (!anchorHref.isEmpty()) + tag += QLatin1String("</a>"); + return tag; +} + +Q3TextFormat Q3TextFormat::makeTextFormat(const Q3StyleSheetItem *style, const QMap<QString,QString>& attr, double scaleFontsFactor) const +{ + Q3TextFormat format(*this); + if (!style) + return format; + + if (!style->isAnchor() && style->color().isValid()) { + // the style is not an anchor and defines a color. + // It might be used inside an anchor and it should + // override the link color. + format.linkColor = false; + } + switch (style->verticalAlignment()) { + case Q3StyleSheetItem::VAlignBaseline: + format.setVAlign(Q3TextFormat::AlignNormal); + break; + case Q3StyleSheetItem::VAlignSuper: + format.setVAlign(Q3TextFormat::AlignSuperScript); + break; + case Q3StyleSheetItem::VAlignSub: + format.setVAlign(Q3TextFormat::AlignSubScript); + break; + } + + if (style->fontWeight() != Q3StyleSheetItem::Undefined) + format.fn.setWeight(style->fontWeight()); + if (style->fontSize() != Q3StyleSheetItem::Undefined) { + format.fn.setPointSize(style->fontSize()); + } else if (style->logicalFontSize() != Q3StyleSheetItem::Undefined) { + format.logicalFontSize = style->logicalFontSize(); + if (format.usePixelSizes) + format.fn.setPixelSize(format.stdSize); + else + format.fn.setPointSize(format.stdSize); + style->styleSheet()->scaleFont(format.fn, format.logicalFontSize); + } else if (style->logicalFontSizeStep()) { + format.logicalFontSize += style->logicalFontSizeStep(); + if (format.usePixelSizes) + format.fn.setPixelSize(format.stdSize); + else + format.fn.setPointSize(format.stdSize); + style->styleSheet()->scaleFont(format.fn, format.logicalFontSize); + } + if (!style->fontFamily().isEmpty()) + format.fn.setFamily(style->fontFamily()); + if (style->color().isValid()) + format.col = style->color(); + if (style->definesFontItalic()) + format.fn.setItalic(style->fontItalic()); + if (style->definesFontUnderline()) + format.fn.setUnderline(style->fontUnderline()); + if (style->definesFontStrikeOut()) + format.fn.setStrikeOut(style->fontStrikeOut()); + + QMap<QString, QString>::ConstIterator it, end = attr.end(); + + if (style->name() == QLatin1String("font")) { + it = attr.find(QLatin1String("color")); + if (it != end && ! (*it).isEmpty()){ + format.col.setNamedColor(*it); + format.linkColor = false; + } + it = attr.find(QLatin1String("face")); + if (it != end) { + QString family = (*it).section(QLatin1Char(','), 0, 0); + if (family.size()) + format.fn.setFamily(family); + } + it = attr.find(QLatin1String("size")); + if (it != end) { + QString a = *it; + int n = a.toInt(); + if (a[0] == QLatin1Char('+') || a[0] == QLatin1Char('-')) + n += 3; + format.logicalFontSize = n; + if (format.usePixelSizes) + format.fn.setPixelSize(format.stdSize); + else + format.fn.setPointSize(format.stdSize); + style->styleSheet()->scaleFont(format.fn, format.logicalFontSize); + } + } + + it = attr.find(QLatin1String("style")); + if (it != end) { + QString a = *it; + int count = a.count(QLatin1Char(';'))+1; + for (int s = 0; s < count; s++) { + QString style = a.section(QLatin1Char(';'), s, s); + if (style.startsWith(QLatin1String("font-size:")) && style.endsWith(QLatin1String("pt"))) { + format.logicalFontSize = 0; + int size = int(scaleFontsFactor * style.mid(10, style.length() - 12).toDouble()); + format.setPointSize(size); + } else if (style.startsWith(QLatin1String("font-style:"))) { + QString s = style.mid(11).trimmed(); + if (s == QLatin1String("normal")) + format.fn.setItalic(false); + else if (s == QLatin1String("italic") || s == QLatin1String("oblique")) + format.fn.setItalic(true); + } else if (style.startsWith(QLatin1String("font-weight:"))) { + QString s = style.mid(12); + bool ok = true; + int n = s.toInt(&ok); + if (ok) + format.fn.setWeight(n/8); + } else if (style.startsWith(QLatin1String("font-family:"))) { + QString family = style.mid(12).section(QLatin1Char(','),0,0); + family.replace(QLatin1Char('\"'), QLatin1Char(' ')); + family.replace(QLatin1Char('\''), QLatin1Char(' ')); + family = family.trimmed(); + format.fn.setFamily(family); + } else if (style.startsWith(QLatin1String("text-decoration:"))) { + QString s = style.mid( 16 ); + format.fn.setOverline(s.contains(QLatin1String("overline"))); + format.fn.setStrikeOut(s.contains(QLatin1String("line-through"))); + format.fn.setUnderline(s.contains(QLatin1String("underline"))); + } else if (style.startsWith(QLatin1String("vertical-align:"))) { + QString s = style.mid(15).trimmed(); + if (s == QLatin1String("sub")) + format.setVAlign(Q3TextFormat::AlignSubScript); + else if (s == QLatin1String("super")) + format.setVAlign(Q3TextFormat::AlignSuperScript); + else + format.setVAlign(Q3TextFormat::AlignNormal); + } else if (style.startsWith(QLatin1String("color:"))) { + format.col.setNamedColor(style.mid(6)); + format.linkColor = false; + } + } + } + + format.update(); + return format; +} + +#ifndef QT_NO_TEXTCUSTOMITEM + +struct QPixmapInt +{ + QPixmapInt() : ref(0) {} + QPixmap pm; + int ref; + Q_DUMMY_COMPARISON_OPERATOR(QPixmapInt) +}; + +static QMap<QString, QPixmapInt> *pixmap_map = 0; + +Q3TextImage::Q3TextImage(Q3TextDocument *p, const QMap<QString, QString> &attr, const QString& context, + Q3MimeSourceFactory &factory) + : Q3TextCustomItem(p) +{ + width = height = 0; + + QMap<QString, QString>::ConstIterator it, end = attr.end(); + it = attr.find(QLatin1String("width")); + if (it != end) + width = (*it).toInt(); + it = attr.find(QLatin1String("height")); + if (it != end) + height = (*it).toInt(); + + reg = 0; + QString imageName = attr[QLatin1String("src")]; + + if (imageName.size() == 0) + imageName = attr[QLatin1String("source")]; + + if (!imageName.isEmpty()) { + imgId = QString(QLatin1String("%1,%2,%3,%4")).arg(imageName).arg(width).arg(height).arg((ulong)&factory); + if (!pixmap_map) + pixmap_map = new QMap<QString, QPixmapInt>; + if (pixmap_map->contains(imgId)) { + QPixmapInt& pmi = pixmap_map->operator[](imgId); + pm = pmi.pm; + pmi.ref++; + width = pm.width(); + height = pm.height(); + } else { + QImage img; + const QMimeSource* m = + factory.data(imageName, context); + if (!m) { + qCritical("Q3TextImage: no mimesource for %s", imageName.latin1()); + } + else { + if (!Q3ImageDrag::decode(m, img)) { + qCritical("Q3TextImage: cannot decode %s", imageName.latin1()); + } + } + + if (!img.isNull()) { + if (width == 0) { + width = img.width(); + if (height != 0) { + width = img.width() * height / img.height(); + } + } + if (height == 0) { + height = img.height(); + if (width != img.width()) { + height = img.height() * width / img.width(); + } + } + if (img.width() != width || img.height() != height){ +#ifndef QT_NO_IMAGE_SMOOTHSCALE + img = img.smoothScale(width, height); +#endif + width = img.width(); + height = img.height(); + } + pm.convertFromImage(img); + } + if (!pm.isNull()) { + QPixmapInt& pmi = pixmap_map->operator[](imgId); + pmi.pm = pm; + pmi.ref++; + } + } + if (pm.hasAlphaChannel()) { + QRegion mask(pm.mask()); + QRegion all(0, 0, pm.width(), pm.height()); + reg = new QRegion(all.subtracted(mask)); + } + } + + if (pm.isNull() && (width*height)==0) + width = height = 50; + + place = PlaceInline; + if (attr[QLatin1String("align")] == QLatin1String("left")) + place = PlaceLeft; + else if (attr[QLatin1String("align")] == QLatin1String("right")) + place = PlaceRight; + + tmpwidth = width; + tmpheight = height; + + attributes = attr; +} + +Q3TextImage::~Q3TextImage() +{ + if (pixmap_map && pixmap_map->contains(imgId)) { + QPixmapInt& pmi = pixmap_map->operator[](imgId); + pmi.ref--; + if (!pmi.ref) { + pixmap_map->remove(imgId); + if (pixmap_map->isEmpty()) { + delete pixmap_map; + pixmap_map = 0; + } + } + } + delete reg; +} + +QString Q3TextImage::richText() const +{ + QString s; + s += QLatin1String("<img "); + QMap<QString, QString>::ConstIterator it = attributes.begin(); + for (; it != attributes.end(); ++it) { + s += it.key() + QLatin1String("="); + if ((*it).contains(QLatin1Char(' '))) + s += QLatin1String("\"") + *it + QLatin1String("\" "); + else + s += *it + QLatin1String(" "); + } + s += QLatin1String(">"); + return s; +} + +void Q3TextImage::adjustToPainter(QPainter* p) +{ + width = scale(tmpwidth, p); + height = scale(tmpheight, p); +} + +#if !defined(Q_WS_X11) +static QPixmap *qrt_selection = 0; +static Q3SingleCleanupHandler<QPixmap> qrt_cleanup_pixmap; +static void qrt_createSelectionPixmap(const QPalette &pal) +{ + qrt_selection = new QPixmap(2, 2); + qrt_cleanup_pixmap.set(&qrt_selection); + qrt_selection->fill(Qt::color0); + QBitmap m(2, 2); + m.fill(Qt::color1); + QPainter p(&m); + p.setPen(Qt::color0); + for (int j = 0; j < 2; ++j) { + p.drawPoint(j % 2, j); + } + p.end(); + qrt_selection->setMask(m); + qrt_selection->fill(pal.highlight().color()); +} +#endif + +void Q3TextImage::draw(QPainter* p, int x, int y, int cx, int cy, int cw, int ch, + const QPalette &pal, bool selected) +{ + if (placement() != PlaceInline) { + x = xpos; + y = ypos; + } + + if (pm.isNull()) { + p->fillRect(x , y, width, height, pal.dark()); + return; + } + + if (is_printer(p)) { + p->drawPixmap(QRect(x, y, width, height), pm); + return; + } + + if (placement() != PlaceInline && !QRect(xpos, ypos, width, height).intersects(QRect(cx, cy, cw, ch))) + return; + + if (placement() == PlaceInline) + p->drawPixmap(x , y, pm); + else + p->drawPixmap(cx , cy, pm, cx - x, cy - y, cw, ch); + + if (selected && placement() == PlaceInline && is_printer(p)) { +#if defined(Q_WS_X11) + p->fillRect(QRect(QPoint(x, y), pm.size()), QBrush(pal.highlight(), + Qt::Dense4Pattern)); +#else // in WIN32 Qt::Dense4Pattern doesn't work correctly (transparency problem), so work around it + if (!qrt_selection) + qrt_createSelectionPixmap(pal); + p->drawTiledPixmap(x, y, pm.width(), pm.height(), *qrt_selection); +#endif + } +} + +void Q3TextHorizontalLine::adjustToPainter(QPainter* p) +{ + height = scale(tmpheight, p); +} + + +Q3TextHorizontalLine::Q3TextHorizontalLine(Q3TextDocument *p, const QMap<QString, QString> &attr, + const QString &, + Q3MimeSourceFactory &) + : Q3TextCustomItem(p) +{ + height = tmpheight = 8; + QMap<QString, QString>::ConstIterator it, end = attr.end(); + it = attr.find(QLatin1String("color")); + if (it != end) + color = QColor(*it); + shade = attr.find(QLatin1String("noshade")) == end; +} + +Q3TextHorizontalLine::~Q3TextHorizontalLine() +{ +} + +QString Q3TextHorizontalLine::richText() const +{ + return QLatin1String("<hr>"); +} + +void Q3TextHorizontalLine::draw(QPainter* p, int x, int y, int , int , int , int , + const QPalette& pal, bool selected) +{ + QRect r(x, y, width, height); + if (is_printer(p) || !shade) { + QPen oldPen = p->pen(); + if (!color.isValid()) + p->setPen(QPen(pal.text().color(), is_printer(p) ? height/8 : qMax(2, height/4))); + else + p->setPen(QPen(color, is_printer(p) ? height/8 : qMax(2, height/4))); + p->drawLine(r.left()-1, y + height / 2, r.right() + 1, y + height / 2); + p->setPen(oldPen); + } else { + if (selected) + p->fillRect(r, pal.highlight()); + QPalette pal2(pal); + if (color.isValid()) + pal2.setColor(pal2.currentColorGroup(), QPalette::Dark, color); + qDrawShadeLine(p, r.left() - 1, y + height / 2, r.right() + 1, y + height / 2, pal2, + true, height / 8); + } +} +#endif //QT_NO_TEXTCUSTOMITEM + +/*****************************************************************/ +// Small set of utility functions to make the parser a bit simpler +// + +bool Q3TextDocument::hasPrefix(const QChar* doc, int length, int pos, QChar c) +{ + if (pos + 1 > length) + return false; + return doc[pos].toLower() == c.toLower(); +} + +bool Q3TextDocument::hasPrefix(const QChar* doc, int length, int pos, const QString& s) +{ + if (pos + (int) s.length() > length) + return false; + for (int i = 0; i < (int)s.length(); i++) { + if (doc[pos + i].toLower() != s[i].toLower()) + return false; + } + return true; +} + +#ifndef QT_NO_TEXTCUSTOMITEM +static bool qt_is_cell_in_use(QList<Q3TextTableCell *>& cells, int row, int col) +{ + for (int idx = 0; idx < cells.size(); ++idx) { + Q3TextTableCell *c = cells.at(idx); + if (row >= c->row() && row < c->row() + c->rowspan() + && col >= c->column() && col < c->column() + c->colspan()) + return true; + } + return false; +} + +Q3TextCustomItem* Q3TextDocument::parseTable(const QMap<QString, QString> &attr, const Q3TextFormat &fmt, + const QChar* doc, int length, int& pos, Q3TextParagraph *curpar) +{ + + Q3TextTable* table = new Q3TextTable(this, attr); + int row = -1; + int col = -1; + + QString rowbgcolor; + QString rowalign; + QString tablebgcolor = attr[QLatin1String("bgcolor")]; + + QList<Q3TextTableCell *> multicells; + + QString tagname; + (void) eatSpace(doc, length, pos); + while (pos < length) { + if (hasPrefix(doc, length, pos, QLatin1Char('<'))){ + if (hasPrefix(doc, length, pos+1, QLatin1Char('/'))) { + tagname = parseCloseTag(doc, length, pos); + if (tagname == QLatin1String("table")) { + return table; + } + } else { + QMap<QString, QString> attr2; + bool emptyTag = false; + tagname = parseOpenTag(doc, length, pos, attr2, emptyTag); + if (tagname == QLatin1String("tr")) { + rowbgcolor = attr2[QLatin1String("bgcolor")]; + rowalign = attr2[QLatin1String("align")]; + row++; + col = -1; + } + else if (tagname == QLatin1String("td") || tagname == QLatin1String("th")) { + col++; + while (qt_is_cell_in_use(multicells, row, col)) { + col++; + } + + if (row >= 0 && col >= 0) { + const Q3StyleSheetItem* s = sheet_->item(tagname); + if (!attr2.contains(QLatin1String("bgcolor"))) { + if (!rowbgcolor.isEmpty()) + attr2[QLatin1String("bgcolor")] = rowbgcolor; + else if (!tablebgcolor.isEmpty()) + attr2[QLatin1String("bgcolor")] = tablebgcolor; + } + if (!attr2.contains(QLatin1String("align"))) { + if (!rowalign.isEmpty()) + attr2[QLatin1String("align")] = rowalign; + } + + // extract the cell contents + int end = pos; + while (end < length + && !hasPrefix(doc, length, end, QLatin1String("</td")) + && !hasPrefix(doc, length, end, QLatin1String("<td")) + && !hasPrefix(doc, length, end, QLatin1String("</th")) + && !hasPrefix(doc, length, end, QLatin1String("<th")) + && !hasPrefix(doc, length, end, QLatin1String("<td")) + && !hasPrefix(doc, length, end, QLatin1String("</tr")) + && !hasPrefix(doc, length, end, QLatin1String("<tr")) + && !hasPrefix(doc, length, end, QLatin1String("</table"))) { + if (hasPrefix(doc, length, end, QLatin1String("<table"))) { // nested table + int nested = 1; + ++end; + while (end < length && nested != 0) { + if (hasPrefix(doc, length, end, QLatin1String("</table"))) + nested--; + if (hasPrefix(doc, length, end, QLatin1String("<table"))) + nested++; + end++; + } + } + end++; + } + Q3TextTableCell* cell = new Q3TextTableCell(table, row, col, + attr2, s, fmt.makeTextFormat(s, attr2, scaleFontsFactor), + contxt, *factory_, sheet_, + QString::fromRawData(doc + pos, end - pos)); + cell->richText()->parentPar = curpar; + if (cell->colspan() > 1 || cell->rowspan() > 1) + multicells.append(cell); + col += cell->colspan()-1; + pos = end; + } + } + } + + } else { + ++pos; + } + } + return table; +} +#endif // QT_NO_TEXTCUSTOMITEM + +bool Q3TextDocument::eatSpace(const QChar* doc, int length, int& pos, bool includeNbsp) +{ + int old_pos = pos; + while (pos < length && doc[pos].isSpace() && (includeNbsp || (doc[pos] != QChar(QChar::nbsp)))) + pos++; + return old_pos < pos; +} + +bool Q3TextDocument::eat(const QChar* doc, int length, int& pos, QChar c) +{ + bool ok = pos < length && doc[pos] == c; + if (ok) + pos++; + return ok; +} +/*****************************************************************/ + +struct Entity { + const char * name; + Q_UINT16 code; +}; + +static const Entity entitylist [] = { + { "AElig", 0x00c6 }, + { "Aacute", 0x00c1 }, + { "Acirc", 0x00c2 }, + { "Agrave", 0x00c0 }, + { "Alpha", 0x0391 }, + { "AMP", 38 }, + { "Aring", 0x00c5 }, + { "Atilde", 0x00c3 }, + { "Auml", 0x00c4 }, + { "Beta", 0x0392 }, + { "Ccedil", 0x00c7 }, + { "Chi", 0x03a7 }, + { "Dagger", 0x2021 }, + { "Delta", 0x0394 }, + { "ETH", 0x00d0 }, + { "Eacute", 0x00c9 }, + { "Ecirc", 0x00ca }, + { "Egrave", 0x00c8 }, + { "Epsilon", 0x0395 }, + { "Eta", 0x0397 }, + { "Euml", 0x00cb }, + { "Gamma", 0x0393 }, + { "GT", 62 }, + { "Iacute", 0x00cd }, + { "Icirc", 0x00ce }, + { "Igrave", 0x00cc }, + { "Iota", 0x0399 }, + { "Iuml", 0x00cf }, + { "Kappa", 0x039a }, + { "Lambda", 0x039b }, + { "LT", 60 }, + { "Mu", 0x039c }, + { "Ntilde", 0x00d1 }, + { "Nu", 0x039d }, + { "OElig", 0x0152 }, + { "Oacute", 0x00d3 }, + { "Ocirc", 0x00d4 }, + { "Ograve", 0x00d2 }, + { "Omega", 0x03a9 }, + { "Omicron", 0x039f }, + { "Oslash", 0x00d8 }, + { "Otilde", 0x00d5 }, + { "Ouml", 0x00d6 }, + { "Phi", 0x03a6 }, + { "Pi", 0x03a0 }, + { "Prime", 0x2033 }, + { "Psi", 0x03a8 }, + { "QUOT", 34 }, + { "Rho", 0x03a1 }, + { "Scaron", 0x0160 }, + { "Sigma", 0x03a3 }, + { "THORN", 0x00de }, + { "Tau", 0x03a4 }, + { "Theta", 0x0398 }, + { "Uacute", 0x00da }, + { "Ucirc", 0x00db }, + { "Ugrave", 0x00d9 }, + { "Upsilon", 0x03a5 }, + { "Uuml", 0x00dc }, + { "Xi", 0x039e }, + { "Yacute", 0x00dd }, + { "Yuml", 0x0178 }, + { "Zeta", 0x0396 }, + { "aacute", 0x00e1 }, + { "acirc", 0x00e2 }, + { "acute", 0x00b4 }, + { "aelig", 0x00e6 }, + { "agrave", 0x00e0 }, + { "alefsym", 0x2135 }, + { "alpha", 0x03b1 }, + { "amp", 38 }, + { "and", 0x22a5 }, + { "ang", 0x2220 }, + { "apos", 0x0027 }, + { "aring", 0x00e5 }, + { "asymp", 0x2248 }, + { "atilde", 0x00e3 }, + { "auml", 0x00e4 }, + { "bdquo", 0x201e }, + { "beta", 0x03b2 }, + { "brvbar", 0x00a6 }, + { "bull", 0x2022 }, + { "cap", 0x2229 }, + { "ccedil", 0x00e7 }, + { "cedil", 0x00b8 }, + { "cent", 0x00a2 }, + { "chi", 0x03c7 }, + { "circ", 0x02c6 }, + { "clubs", 0x2663 }, + { "cong", 0x2245 }, + { "copy", 0x00a9 }, + { "crarr", 0x21b5 }, + { "cup", 0x222a }, + { "cur" "ren", 0x00a4 }, + { "dArr", 0x21d3 }, + { "dagger", 0x2020 }, + { "darr", 0x2193 }, + { "deg", 0x00b0 }, + { "delta", 0x03b4 }, + { "diams", 0x2666 }, + { "divide", 0x00f7 }, + { "eacute", 0x00e9 }, + { "ecirc", 0x00ea }, + { "egrave", 0x00e8 }, + { "empty", 0x2205 }, + { "emsp", 0x2003 }, + { "ensp", 0x2002 }, + { "epsilon", 0x03b5 }, + { "equiv", 0x2261 }, + { "eta", 0x03b7 }, + { "eth", 0x00f0 }, + { "euml", 0x00eb }, + { "euro", 0x20ac }, + { "exist", 0x2203 }, + { "fnof", 0x0192 }, + { "forall", 0x2200 }, + { "frac12", 0x00bd }, + { "frac14", 0x00bc }, + { "frac34", 0x00be }, + { "frasl", 0x2044 }, + { "gamma", 0x03b3 }, + { "ge", 0x2265 }, + { "gt", 62 }, + { "hArr", 0x21d4 }, + { "harr", 0x2194 }, + { "hearts", 0x2665 }, + { "hellip", 0x2026 }, + { "iacute", 0x00ed }, + { "icirc", 0x00ee }, + { "iexcl", 0x00a1 }, + { "igrave", 0x00ec }, + { "image", 0x2111 }, + { "infin", 0x221e }, + { "int", 0x222b }, + { "iota", 0x03b9 }, + { "iquest", 0x00bf }, + { "isin", 0x2208 }, + { "iuml", 0x00ef }, + { "kappa", 0x03ba }, + { "lArr", 0x21d0 }, + { "lambda", 0x03bb }, + { "lang", 0x2329 }, + { "laquo", 0x00ab }, + { "larr", 0x2190 }, + { "lceil", 0x2308 }, + { "ldquo", 0x201c }, + { "le", 0x2264 }, + { "lfloor", 0x230a }, + { "lowast", 0x2217 }, + { "loz", 0x25ca }, + { "lrm", 0x200e }, + { "lsaquo", 0x2039 }, + { "lsquo", 0x2018 }, + { "lt", 60 }, + { "macr", 0x00af }, + { "mdash", 0x2014 }, + { "micro", 0x00b5 }, + { "middot", 0x00b7 }, + { "minus", 0x2212 }, + { "mu", 0x03bc }, + { "nabla", 0x2207 }, + { "nbsp", 0x00a0 }, + { "ndash", 0x2013 }, + { "ne", 0x2260 }, + { "ni", 0x220b }, + { "not", 0x00ac }, + { "notin", 0x2209 }, + { "nsub", 0x2284 }, + { "ntilde", 0x00f1 }, + { "nu", 0x03bd }, + { "oacute", 0x00f3 }, + { "ocirc", 0x00f4 }, + { "oelig", 0x0153 }, + { "ograve", 0x00f2 }, + { "oline", 0x203e }, + { "omega", 0x03c9 }, + { "omicron", 0x03bf }, + { "oplus", 0x2295 }, + { "or", 0x22a6 }, + { "ordf", 0x00aa }, + { "ordm", 0x00ba }, + { "oslash", 0x00f8 }, + { "otilde", 0x00f5 }, + { "otimes", 0x2297 }, + { "ouml", 0x00f6 }, + { "para", 0x00b6 }, + { "part", 0x2202 }, + { "percnt", 0x0025 }, + { "permil", 0x2030 }, + { "perp", 0x22a5 }, + { "phi", 0x03c6 }, + { "pi", 0x03c0 }, + { "piv", 0x03d6 }, + { "plusmn", 0x00b1 }, + { "pound", 0x00a3 }, + { "prime", 0x2032 }, + { "prod", 0x220f }, + { "prop", 0x221d }, + { "psi", 0x03c8 }, + { "quot", 34 }, + { "rArr", 0x21d2 }, + { "radic", 0x221a }, + { "rang", 0x232a }, + { "raquo", 0x00bb }, + { "rarr", 0x2192 }, + { "rceil", 0x2309 }, + { "rdquo", 0x201d }, + { "real", 0x211c }, + { "reg", 0x00ae }, + { "rfloor", 0x230b }, + { "rho", 0x03c1 }, + { "rlm", 0x200f }, + { "rsaquo", 0x203a }, + { "rsquo", 0x2019 }, + { "sbquo", 0x201a }, + { "scaron", 0x0161 }, + { "sdot", 0x22c5 }, + { "sect", 0x00a7 }, + { "shy", 0x00ad }, + { "sigma", 0x03c3 }, + { "sigmaf", 0x03c2 }, + { "sim", 0x223c }, + { "spades", 0x2660 }, + { "sub", 0x2282 }, + { "sube", 0x2286 }, + { "sum", 0x2211 }, + { "sup1", 0x00b9 }, + { "sup2", 0x00b2 }, + { "sup3", 0x00b3 }, + { "sup", 0x2283 }, + { "supe", 0x2287 }, + { "szlig", 0x00df }, + { "tau", 0x03c4 }, + { "there4", 0x2234 }, + { "theta", 0x03b8 }, + { "thetasym", 0x03d1 }, + { "thinsp", 0x2009 }, + { "thorn", 0x00fe }, + { "tilde", 0x02dc }, + { "times", 0x00d7 }, + { "trade", 0x2122 }, + { "uArr", 0x21d1 }, + { "uacute", 0x00fa }, + { "uarr", 0x2191 }, + { "ucirc", 0x00fb }, + { "ugrave", 0x00f9 }, + { "uml", 0x00a8 }, + { "upsih", 0x03d2 }, + { "upsilon", 0x03c5 }, + { "uuml", 0x00fc }, + { "weierp", 0x2118 }, + { "xi", 0x03be }, + { "yacute", 0x00fd }, + { "yen", 0x00a5 }, + { "yuml", 0x00ff }, + { "zeta", 0x03b6 }, + { "zwj", 0x200d }, + { "zwnj", 0x200c }, + { "", 0x0000 } +}; + + + + + +static QMap<QByteArray, QChar> *html_map = 0; +static void qt_cleanup_html_map() +{ + delete html_map; + html_map = 0; +} + +static QMap<QByteArray, QChar> *htmlMap() +{ + if (!html_map) { + html_map = new QMap<QByteArray, QChar>; + qAddPostRoutine(qt_cleanup_html_map); + + const Entity *ent = entitylist; + while(ent->code) { + html_map->insert(QByteArray(ent->name), QChar(ent->code)); + ent++; + } + } + return html_map; +} + +QChar Q3TextDocument::parseHTMLSpecialChar(const QChar* doc, int length, int& pos) +{ + QString s; + pos++; + int recoverpos = pos; + while (pos < length && doc[pos] != QLatin1Char(';') && !doc[pos].isSpace() && pos < recoverpos + 8) { + s += doc[pos]; + pos++; + } + if (doc[pos] != QLatin1Char(';') && !doc[pos].isSpace()) { + pos = recoverpos; + return QLatin1Char('&'); + } + pos++; + + if (s.length() > 1 && s[0] == QLatin1Char('#')) { + int off = 1; + int base = 10; + if (s[1] == QLatin1Char('x')) { + off = 2; + base = 16; + } + bool ok; + int num = s.mid(off).toInt(&ok, base); + if (num == 151) // ### hack for designer manual + return QLatin1Char('-'); + return num; + } + + QMap<QByteArray, QChar>::Iterator it = htmlMap()->find(s.toLatin1()); + if (it != htmlMap()->end()) { + return *it; + } + + pos = recoverpos; + return QLatin1Char('&'); +} + +QString Q3TextDocument::parseWord(const QChar* doc, int length, int& pos, bool lower) +{ + QString s; + + if (doc[pos] == QLatin1Char('"')) { + pos++; + while (pos < length && doc[pos] != QLatin1Char('"')) { + if (doc[pos] == QLatin1Char('&')) { + s += parseHTMLSpecialChar(doc, length, pos); + } else { + s += doc[pos]; + pos++; + } + } + eat(doc, length, pos, QLatin1Char('"')); + } else if (doc[pos] == QLatin1Char('\'')) { + pos++; + while (pos < length && doc[pos] != QLatin1Char('\'')) { + s += doc[pos]; + pos++; + } + eat(doc, length, pos, QLatin1Char('\'')); + } else { + static QString term = QString::fromLatin1("/>"); + while (pos < length + && doc[pos] != QLatin1Char('>') + && !hasPrefix(doc, length, pos, term) + && doc[pos] != QLatin1Char('<') + && doc[pos] != QLatin1Char('=') + && !doc[pos].isSpace()) + { + if (doc[pos] == QLatin1Char('&')) { + s += parseHTMLSpecialChar(doc, length, pos); + } else { + s += doc[pos]; + pos++; + } + } + if (lower) + s = s.toLower(); + } + return s; +} + +QChar Q3TextDocument::parseChar(const QChar* doc, int length, int& pos, Q3StyleSheetItem::WhiteSpaceMode wsm) +{ + if (pos >= length) + return QChar::null; + + QChar c = doc[pos++]; + + if (c == QLatin1Char('<')) + return QChar::null; + + if (c.isSpace() && c != QChar(QChar::nbsp)) { + if (wsm == Q3StyleSheetItem::WhiteSpacePre) { + if (c == QLatin1Char('\n')) + return QChar::LineSeparator; + else + return c; + } else { // non-pre mode: collapse whitespace except nbsp + while (pos< length && + doc[pos].isSpace() && doc[pos] != QChar(QChar::nbsp)) + pos++; + return QLatin1Char(' '); + } + } + else if (c == QLatin1Char('&')) + return parseHTMLSpecialChar(doc, length, --pos); + else + return c; +} + +QString Q3TextDocument::parseOpenTag(const QChar* doc, int length, int& pos, + QMap<QString, QString> &attr, bool& emptyTag) +{ + emptyTag = false; + pos++; + if (hasPrefix(doc, length, pos, QLatin1Char('!'))) { + if (hasPrefix(doc, length, pos+1, QLatin1String("--"))) { + pos += 3; + // eat comments + QString pref = QString::fromLatin1("-->"); + while (!hasPrefix(doc, length, pos, pref) && pos < length) + pos++; + if (hasPrefix(doc, length, pos, pref)) { + pos += 3; + eatSpace(doc, length, pos, true); + } + emptyTag = true; + return QString(); + } + else { + // eat strange internal tags + while (!hasPrefix(doc, length, pos, QLatin1Char('>')) && pos < length) + pos++; + if (hasPrefix(doc, length, pos, QLatin1Char('>'))) { + pos++; + eatSpace(doc, length, pos, true); + } + return QString(); + } + } + + QString tag = parseWord(doc, length, pos); + eatSpace(doc, length, pos, true); + static QString term = QString::fromLatin1("/>"); + static QString s_TRUE = QString::fromLatin1("TRUE"); + + while (doc[pos] != QLatin1Char('>') && ! (emptyTag = hasPrefix(doc, length, pos, term))) { + QString key = parseWord(doc, length, pos); + eatSpace(doc, length, pos, true); + if (key.isEmpty()) { + // error recovery + while (pos < length && doc[pos] != QLatin1Char('>')) + pos++; + break; + } + QString value; + if (hasPrefix(doc, length, pos, QLatin1Char('='))){ + pos++; + eatSpace(doc, length, pos); + value = parseWord(doc, length, pos, false); + } + else + value = s_TRUE; + attr.insert(key.toLower(), value); + eatSpace(doc, length, pos, true); + } + + if (emptyTag) { + eat(doc, length, pos, QLatin1Char('/')); + eat(doc, length, pos, QLatin1Char('>')); + } + else + eat(doc, length, pos, QLatin1Char('>')); + + return tag; +} + +QString Q3TextDocument::parseCloseTag(const QChar* doc, int length, int& pos) +{ + pos++; + pos++; + QString tag = parseWord(doc, length, pos); + eatSpace(doc, length, pos, true); + eat(doc, length, pos, QLatin1Char('>')); + return tag; +} + +Q3TextFlow::Q3TextFlow() +{ + w = pagesize = 0; +} + +Q3TextFlow::~Q3TextFlow() +{ + clear(); +} + +void Q3TextFlow::clear() +{ +#ifndef QT_NO_TEXTCUSTOMITEM + while (!leftItems.isEmpty()) + delete leftItems.takeFirst(); + while (!rightItems.isEmpty()) + delete rightItems.takeFirst(); +#endif +} + +void Q3TextFlow::setWidth(int width) +{ + w = width; +} + +int Q3TextFlow::adjustLMargin(int yp, int, int margin, int space) +{ +#ifndef QT_NO_TEXTCUSTOMITEM + for (int idx = 0; idx < leftItems.size(); ++idx) { + Q3TextCustomItem* item = leftItems.at(idx); + if (item->ypos == -1) + continue; + if (yp >= item->ypos && yp < item->ypos + item->height) + margin = qMax(margin, item->xpos + item->width + space); + } +#endif + return margin; +} + +int Q3TextFlow::adjustRMargin(int yp, int, int margin, int space) +{ +#ifndef QT_NO_TEXTCUSTOMITEM + for (int idx = 0; idx < rightItems.size(); ++idx) { + Q3TextCustomItem* item = rightItems.at(idx); + if (item->ypos == -1) + continue; + if (yp >= item->ypos && yp < item->ypos + item->height) + margin = qMax(margin, w - item->xpos - space); + } +#endif + return margin; +} + + +int Q3TextFlow::adjustFlow(int y, int /*w*/, int h) +{ + if (pagesize > 0) { // check pages + int yinpage = y % pagesize; + if (yinpage <= border_tolerance) + return border_tolerance - yinpage; + else + if (yinpage + h > pagesize - border_tolerance) + return (pagesize - yinpage) + border_tolerance; + } + return 0; +} + +#ifndef QT_NO_TEXTCUSTOMITEM +void Q3TextFlow::unregisterFloatingItem(Q3TextCustomItem* item) +{ + leftItems.removeAll(item); + rightItems.removeAll(item); +} + +void Q3TextFlow::registerFloatingItem(Q3TextCustomItem* item) +{ + if (item->placement() == Q3TextCustomItem::PlaceRight) { + if (!rightItems.contains(item)) + rightItems.append(item); + } else if (item->placement() == Q3TextCustomItem::PlaceLeft && + !leftItems.contains(item)) { + leftItems.append(item); + } +} +#endif // QT_NO_TEXTCUSTOMITEM + +QRect Q3TextFlow::boundingRect() const +{ + QRect br; +#ifndef QT_NO_TEXTCUSTOMITEM + for (int idx = 0; idx < leftItems.size(); ++idx) { + Q3TextCustomItem* item = leftItems.at(idx); + br = br.united(item->geometry()); + } + for (int idx = 0; idx < rightItems.size(); ++idx) { + Q3TextCustomItem* item = rightItems.at(idx); + br = br.united(item->geometry()); + } +#endif + return br; +} + + +void Q3TextFlow::drawFloatingItems(QPainter* p, int cx, int cy, int cw, int ch, + const QPalette &pal, bool selected) +{ +#ifndef QT_NO_TEXTCUSTOMITEM + for (int idx = 0; idx < leftItems.size(); ++idx) { + Q3TextCustomItem* item = leftItems.at(idx); + if (item->xpos == -1 || item->ypos == -1) + continue; + item->draw(p, item->xpos, item->ypos, cx, cy, cw, ch, pal, selected); + } + + for (int idx = 0; idx < rightItems.size(); ++idx) { + Q3TextCustomItem* item = rightItems.at(idx); + if (item->xpos == -1 || item->ypos == -1) + continue; + item->draw(p, item->xpos, item->ypos, cx, cy, cw, ch, pal, selected); + } +#endif +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +#ifndef QT_NO_TEXTCUSTOMITEM +void Q3TextCustomItem::pageBreak(int /*y*/ , Q3TextFlow* /*flow*/) +{ +} +#endif + +#ifndef QT_NO_TEXTCUSTOMITEM +Q3TextTable::Q3TextTable(Q3TextDocument *p, const QMap<QString, QString> & attr ) + : Q3TextCustomItem(p) +{ + cellspacing = 2; + cellpadding = 1; + border = innerborder = 0; + + QMap<QString, QString>::ConstIterator it, end = attr.end(); + it = attr.find(QLatin1String("cellspacing")); + if (it != end) + cellspacing = (*it).toInt(); + it = attr.find(QLatin1String("cellpadding")); + if (it != end) + cellpadding = (*it).toInt(); + it = attr.find(QLatin1String("border")); + if (it != end) { + if (*it == QLatin1String("TRUE")) + border = 1; + else + border = (*it).toInt(); + } + us_b = border; + + innerborder = us_ib = border ? 1 : 0; + + if (border) + cellspacing += 2; + + us_ib = innerborder; + us_cs = cellspacing; + us_cp = cellpadding; + outerborder = cellspacing + border; + us_ob = outerborder; + layout = new QGridLayout(1, 1, cellspacing); + + fixwidth = 0; + stretch = 0; + it = attr.find(QLatin1String("width")); + if (it != end) { + bool b; + QString s(*it); + int w = s.toInt(&b); + if (b) { + fixwidth = w; + } else { + s = s.trimmed(); + if (s.length() > 1 && s[(int)s.length()-1] == QLatin1Char('%')) + stretch = s.left(s.length()-1).toInt(); + } + } + us_fixwidth = fixwidth; + + place = PlaceInline; + if (attr[QLatin1String("align")] == QLatin1String("left")) + place = PlaceLeft; + else if (attr[QLatin1String("align")] == QLatin1String("right")) + place = PlaceRight; + cachewidth = 0; + attributes = attr; + pageBreakFor = -1; +} + +Q3TextTable::~Q3TextTable() +{ + delete layout; +} + +QString Q3TextTable::richText() const +{ + QString s; + s = QLatin1String("<table "); + QMap<QString, QString>::ConstIterator it = attributes.begin(); + for (; it != attributes.end(); ++it) + s += it.key() + QLatin1String("=") + *it + QLatin1String(" "); + s += QLatin1String(">\n"); + + int lastRow = -1; + bool needEnd = false; + for (int idx = 0; idx < cells.size(); ++idx) { + Q3TextTableCell *cell = cells.at(idx); + if (lastRow != cell->row()) { + if (lastRow != -1) + s += QLatin1String("</tr>\n"); + s += QLatin1String("<tr>"); + lastRow = cell->row(); + needEnd = true; + } + s += QLatin1String("<td"); + it = cell->attributes.constBegin(); + for (; it != cell->attributes.constEnd(); ++it) + s += QLatin1String(" ") + it.key() + QLatin1String("=") + *it; + s += QLatin1String(">"); + s += cell->richText()->richText(); + s += QLatin1String("</td>"); + } + if (needEnd) + s += QLatin1String("</tr>\n"); + s += QLatin1String("</table>\n"); + return s; +} + +void Q3TextTable::adjustToPainter(QPainter* p) +{ + cellspacing = scale(us_cs, p); + cellpadding = scale(us_cp, p); + border = scale(us_b , p); + innerborder = scale(us_ib, p); + outerborder = scale(us_ob ,p); + fixwidth = scale( us_fixwidth, p); + width = 0; + cachewidth = 0; + for (int idx = 0; idx < cells.size(); ++idx) { + Q3TextTableCell *cell = cells.at(idx); + cell->adjustToPainter(p); + } +} + +void Q3TextTable::adjustCells(int y , int shift) +{ + bool enlarge = false; + for (int idx = 0; idx < cells.size(); ++idx) { + Q3TextTableCell *cell = cells.at(idx); + QRect r = cell->geometry(); + if (y <= r.top()) { + r.moveBy(0, shift); + cell->setGeometry(r); + enlarge = true; + } else if (y <= r.bottom()) { + r.rBottom() += shift; + cell->setGeometry(r); + enlarge = true; + } + } + if (enlarge) + height += shift; +} + +void Q3TextTable::pageBreak(int yt, Q3TextFlow* flow) +{ + if (flow->pageSize() <= 0) + return; + if (layout && pageBreakFor > 0 && pageBreakFor != yt) { + layout->invalidate(); + int h = layout->heightForWidth(width-2*outerborder); + layout->setGeometry(QRect(0, 0, width-2*outerborder, h) ); + height = layout->geometry().height()+2*outerborder; + } + pageBreakFor = yt; + for (int idx = 0; idx < cells.size(); ++idx) { + Q3TextTableCell *cell = cells.at(idx); + int y = yt + outerborder + cell->geometry().y(); + int shift = flow->adjustFlow(y - cellspacing, width, cell->richText()->height() + 2*cellspacing); + adjustCells(y - outerborder - yt, shift); + } +} + + +void Q3TextTable::draw(QPainter* p, int x, int y, int cx, int cy, int cw, int ch, + const QPalette &pal, bool selected) +{ + if (placement() != PlaceInline) { + x = xpos; + y = ypos; + } + + for (int idx = 0; idx < cells.size(); ++idx) { + Q3TextTableCell *cell = cells.at(idx); + if ((cx < 0 && cy < 0) || + QRect(cx, cy, cw, ch).intersects(QRect(x + outerborder + cell->geometry().x(), + y + outerborder + cell->geometry().y(), + cell->geometry().width(), + cell->geometry().height()))) { + cell->draw(p, x+outerborder, y+outerborder, cx, cy, cw, ch, pal, selected); + if (border) { + QRect r(x+outerborder+cell->geometry().x() - innerborder, + y+outerborder+cell->geometry().y() - innerborder, + cell->geometry().width() + 2 * innerborder, + cell->geometry().height() + 2 * innerborder); + if (is_printer(p)) { + QPen oldPen = p->pen(); + QRect r2 = r; + r2.adjust(innerborder/2, innerborder/2, -innerborder/2, -innerborder/2); + p->setPen(QPen(pal.text().color(), innerborder)); + p->drawRect(r2); + p->setPen(oldPen); + } else { + int s = qMax(cellspacing-2*innerborder, 0); + if (s) { + p->fillRect(r.left()-s, r.top(), s+1, r.height(), pal.button()); + p->fillRect(r.right(), r.top(), s+1, r.height(), pal.button()); + p->fillRect(r.left()-s, r.top()-s, r.width()+2*s, s, pal.button()); + p->fillRect(r.left()-s, r.bottom(), r.width()+2*s, s, pal.button()); + } + qDrawShadePanel(p, r, pal, true, innerborder); + } + } + } + } + if (border) { + QRect r (x, y, width, height); + if (is_printer(p)) { + QRect r2 = r; + r2.adjust(border/2, border/2, -border/2, -border/2); + QPen oldPen = p->pen(); + p->setPen(QPen(pal.text().color(), border)); + p->drawRect(r2); + p->setPen(oldPen); + } else { + int s = border+qMax(cellspacing-2*innerborder, 0); + if (s) { + p->fillRect(r.left(), r.top(), s, r.height(), pal.button()); + p->fillRect(r.right()-s, r.top(), s, r.height(), pal.button()); + p->fillRect(r.left(), r.top(), r.width(), s, pal.button()); + p->fillRect(r.left(), r.bottom()-s, r.width(), s, pal.button()); + } + qDrawShadePanel(p, r, pal, false, border); + } + } + +} + +int Q3TextTable::minimumWidth() const +{ + return qMax(fixwidth, ((layout ? layout->minimumSize().width() : 0) + 2 * outerborder)); +} + +void Q3TextTable::resize(int nwidth) +{ + if (fixwidth && cachewidth != 0) + return; + if (nwidth == cachewidth) + return; + + + cachewidth = nwidth; + int w = nwidth; + + format(w); + + if (stretch) + nwidth = nwidth * stretch / 100; + + width = nwidth; + layout->invalidate(); + int shw = layout->sizeHint().width() + 2*outerborder; + int mw = layout->minimumSize().width() + 2*outerborder; + if (stretch) + width = qMax(mw, nwidth); + else + width = qMax(mw, qMin(nwidth, shw)); + + if (fixwidth) + width = fixwidth; + + layout->invalidate(); + mw = layout->minimumSize().width() + 2*outerborder; + width = qMax(width, mw); + + int h = layout->heightForWidth(width-2*outerborder); + layout->setGeometry(QRect(0, 0, width-2*outerborder, h) ); + height = layout->geometry().height()+2*outerborder; +} + +void Q3TextTable::format(int w) +{ + for (int i = 0; i < (int)cells.count(); ++i) { + Q3TextTableCell *cell = cells.at(i); + QRect r = cell->geometry(); + r.setWidth(w - 2*outerborder); + cell->setGeometry(r); + } +} + +void Q3TextTable::addCell(Q3TextTableCell* cell) +{ + cells.append(cell); + layout->addMultiCell(cell, cell->row(), cell->row() + cell->rowspan()-1, + cell->column(), cell->column() + cell->colspan()-1); +} + +bool Q3TextTable::enter(Q3TextCursor *c, Q3TextDocument *&doc, Q3TextParagraph *¶g, int &idx, int &ox, int &oy, bool atEnd) +{ + currCell.remove(c); + if (!atEnd) + return next(c, doc, parag, idx, ox, oy); + currCell.insert(c, cells.count()); + return prev(c, doc, parag, idx, ox, oy); +} + +bool Q3TextTable::enterAt(Q3TextCursor *c, Q3TextDocument *&doc, Q3TextParagraph *¶g, int &idx, int &ox, int &oy, const QPoint &pos) +{ + currCell.remove(c); + int lastCell = -1; + int lastY = -1; + int i; + for (i = 0; i < (int)cells.count(); ++i) { + Q3TextTableCell *cell = cells.at(i); + if (!cell) + continue; + QRect r(cell->geometry().x(), + cell->geometry().y(), + cell->geometry().width() + 2 * innerborder + 2 * outerborder, + cell->geometry().height() + 2 * innerborder + 2 * outerborder); + + if (r.left() <= pos.x() && r.right() >= pos.x()) { + if (cell->geometry().y() > lastY) { + lastCell = i; + lastY = cell->geometry().y(); + } + if (r.top() <= pos.y() && r.bottom() >= pos.y()) { + currCell.insert(c, i); + break; + } + } + } + if (i == (int) cells.count()) + return false; // no cell found + + if (currCell.find(c) == currCell.end()) { + if (lastY != -1) + currCell.insert(c, lastCell); + else + return false; + } + + Q3TextTableCell *cell = cells.at(*currCell.find(c)); + if (!cell) + return false; + doc = cell->richText(); + parag = doc->firstParagraph(); + idx = 0; + ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x(); + oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder; + return true; +} + +bool Q3TextTable::next(Q3TextCursor *c, Q3TextDocument *&doc, Q3TextParagraph *¶g, int &idx, int &ox, int &oy) +{ + int cc = -1; + if (currCell.find(c) != currCell.end()) + cc = *currCell.find(c); + if (cc > (int)cells.count() - 1 || cc < 0) + cc = -1; + currCell.remove(c); + currCell.insert(c, ++cc); + if (cc >= (int)cells.count()) { + currCell.insert(c, 0); + Q3TextCustomItem::next(c, doc, parag, idx, ox, oy); + Q3TextTableCell *cell = cells.first(); + if (!cell) + return false; + doc = cell->richText(); + idx = -1; + return true; + } + + if (currCell.find(c) == currCell.end()) + return false; + Q3TextTableCell *cell = cells.at(*currCell.find(c)); + if (!cell) + return false; + doc = cell->richText(); + parag = doc->firstParagraph(); + idx = 0; + ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x(); + oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder; + return true; +} + +bool Q3TextTable::prev(Q3TextCursor *c, Q3TextDocument *&doc, Q3TextParagraph *¶g, int &idx, int &ox, int &oy) +{ + int cc = -1; + if (currCell.find(c) != currCell.end()) + cc = *currCell.find(c); + if (cc > (int)cells.count() - 1 || cc < 0) + cc = cells.count(); + currCell.remove(c); + currCell.insert(c, --cc); + if (cc < 0) { + currCell.insert(c, 0); + Q3TextCustomItem::prev(c, doc, parag, idx, ox, oy); + Q3TextTableCell *cell = cells.first(); + if (!cell) + return false; + doc = cell->richText(); + idx = -1; + return true; + } + + if (currCell.find(c) == currCell.end()) + return false; + Q3TextTableCell *cell = cells.at(*currCell.find(c)); + if (!cell) + return false; + doc = cell->richText(); + parag = doc->lastParagraph(); + idx = parag->length() - 1; + ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x(); + oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder; + return true; +} + +bool Q3TextTable::down(Q3TextCursor *c, Q3TextDocument *&doc, Q3TextParagraph *¶g, int &idx, int &ox, int &oy) +{ + if (currCell.find(c) == currCell.end()) + return false; + Q3TextTableCell *cell = cells.at(*currCell.find(c)); + if (cell->row_ == layout->numRows() - 1) { + currCell.insert(c, 0); + Q3TextCustomItem::down(c, doc, parag, idx, ox, oy); + Q3TextTableCell *cell = cells.first(); + if (!cell) + return false; + doc = cell->richText(); + idx = -1; + return true; + } + + int oldRow = cell->row_; + int oldCol = cell->col_; + if (currCell.find(c) == currCell.end()) + return false; + int cc = *currCell.find(c); + for (int i = cc; i < (int)cells.count(); ++i) { + cell = cells.at(i); + if (cell->row_ > oldRow && cell->col_ == oldCol) { + currCell.insert(c, i); + break; + } + } + doc = cell->richText(); + if (!cell) + return false; + parag = doc->firstParagraph(); + idx = 0; + ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x(); + oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder; + return true; +} + +bool Q3TextTable::up(Q3TextCursor *c, Q3TextDocument *&doc, Q3TextParagraph *¶g, int &idx, int &ox, int &oy) +{ + if (currCell.find(c) == currCell.end()) + return false; + Q3TextTableCell *cell = cells.at(*currCell.find(c)); + if (cell->row_ == 0) { + currCell.insert(c, 0); + Q3TextCustomItem::up(c, doc, parag, idx, ox, oy); + Q3TextTableCell *cell = cells.first(); + if (!cell) + return false; + doc = cell->richText(); + idx = -1; + return true; + } + + int oldRow = cell->row_; + int oldCol = cell->col_; + if (currCell.find(c) == currCell.end()) + return false; + int cc = *currCell.find(c); + for (int i = cc; i >= 0; --i) { + cell = cells.at(i); + if (cell->row_ < oldRow && cell->col_ == oldCol) { + currCell.insert(c, i); + break; + } + } + doc = cell->richText(); + if (!cell) + return false; + parag = doc->lastParagraph(); + idx = parag->length() - 1; + ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x(); + oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder; + return true; +} + +Q3TextTableCell::Q3TextTableCell(Q3TextTable* table, + int row, int column, + const QMap<QString, QString> &attr, + const Q3StyleSheetItem* style, + const Q3TextFormat& fmt, const QString& context, + Q3MimeSourceFactory &factory, Q3StyleSheet *sheet, + const QString& doc) +{ + cached_width = -1; + cached_sizehint = -1; + + maxw = QWIDGETSIZE_MAX; + minw = 0; + + parent = table; + row_ = row; + col_ = column; + stretch_ = 0; + richtext = new Q3TextDocument(table->parent); + richtext->formatCollection()->setPaintDevice(table->parent->formatCollection()->paintDevice()); + richtext->bodyText = fmt.color(); + richtext->setTableCell(this); + + QMap<QString,QString>::ConstIterator it, end = attr.end(); + int halign = style->alignment(); + if (halign != Q3StyleSheetItem::Undefined) + richtext->setAlignment(halign); + it = attr.find(QLatin1String("align")); + if (it != end && ! (*it).isEmpty()) { + QString a = (*it).toLower(); + if (a == QLatin1String("left")) + richtext->setAlignment(Qt::AlignLeft); + else if (a == QLatin1String("center")) + richtext->setAlignment(Qt::AlignHCenter); + else if (a == QLatin1String("right")) + richtext->setAlignment(Qt::AlignRight); + } + align = 0; + it = attr.find(QLatin1String("valign")); + if (it != end && ! (*it).isEmpty()) { + QString va = (*it).toLower(); + if ( va == QLatin1String("top") ) + align |= Qt::AlignTop; + else if ( va == QLatin1String("center") || va == QLatin1String("middle") ) + align |= Qt::AlignVCenter; + else if (va == QLatin1String("bottom")) + align |= Qt::AlignBottom; + } + richtext->setFormatter(table->parent->formatter()); + richtext->setUseFormatCollection(table->parent->useFormatCollection()); + richtext->setMimeSourceFactory(&factory); + richtext->setStyleSheet(sheet); + richtext->setRichText(doc, context, &fmt); + rowspan_ = 1; + colspan_ = 1; + + it = attr.find(QLatin1String("colspan")); + if (it != end) + colspan_ = (*it).toInt(); + it = attr.find(QLatin1String("rowspan")); + if (it != end) + rowspan_ = (*it).toInt(); + + background = 0; + it = attr.find(QLatin1String("bgcolor")); + if (it != end) { + background = new QBrush(QColor(*it)); + } + + hasFixedWidth = false; + it = attr.find(QLatin1String("width")); + if (it != end) { + bool b; + QString s(*it); + int w = s.toInt(&b); + if (b) { + maxw = w; + minw = maxw; + hasFixedWidth = true; + } else { + s = s.trimmed(); + if (s.length() > 1 && s[(int)s.length()-1] == QLatin1Char('%')) + stretch_ = s.left(s.length()-1).toInt(); + } + } + + attributes = attr; + + parent->addCell(this); +} + +Q3TextTableCell::~Q3TextTableCell() +{ + delete background; + background = 0; + delete richtext; + richtext = 0; +} + +QSize Q3TextTableCell::sizeHint() const +{ + int extra = 2 * (parent->innerborder + parent->cellpadding + border_tolerance); + int used = richtext->widthUsed() + extra; + + if (stretch_) { + int w = parent->width * stretch_ / 100 - 2*parent->cellspacing - 2*parent->cellpadding; + return QSize(qMin(w, maxw), 0).expandedTo(minimumSize()); + } + + return QSize(used, 0).expandedTo(minimumSize()); +} + +QSize Q3TextTableCell::minimumSize() const +{ + int extra = 2 * (parent->innerborder + parent->cellpadding + border_tolerance); + return QSize(qMax(richtext->minimumWidth() + extra, minw), 0); +} + +QSize Q3TextTableCell::maximumSize() const +{ + return QSize(maxw, QWIDGETSIZE_MAX); +} + +Qt::Orientations Q3TextTableCell::expandingDirections() const +{ + return Qt::Horizontal | Qt::Vertical; +} + +bool Q3TextTableCell::isEmpty() const +{ + return false; +} +void Q3TextTableCell::setGeometry(const QRect& r) +{ + int extra = 2 * (parent->innerborder + parent->cellpadding); + if (r.width() != cached_width) + richtext->doLayout(Q3TextFormat::painter(), r.width() - extra); + cached_width = r.width(); + geom = r; +} + +QRect Q3TextTableCell::geometry() const +{ + return geom; +} + +bool Q3TextTableCell::hasHeightForWidth() const +{ + return true; +} + +int Q3TextTableCell::heightForWidth(int w) const +{ + int extra = 2 * (parent->innerborder + parent->cellpadding); + w = qMax(minw, w); + + if (cached_width != w) { + Q3TextTableCell* that = (Q3TextTableCell*) this; + that->richtext->doLayout(Q3TextFormat::painter(), w - extra); + that->cached_width = w; + } + return richtext->height() + extra; +} + +void Q3TextTableCell::adjustToPainter(QPainter* p) +{ + Q3TextParagraph *parag = richtext->firstParagraph(); + while (parag) { + parag->adjustToPainter(p); + parag = parag->next(); + } +} + +int Q3TextTableCell::horizontalAlignmentOffset() const +{ + return parent->cellpadding; +} + +int Q3TextTableCell::verticalAlignmentOffset() const +{ + if ((align & Qt::AlignVCenter) == Qt::AlignVCenter) + return (geom.height() - richtext->height()) / 2; + else if ((align & Qt::AlignBottom) == Qt::AlignBottom) + return geom.height() - parent->cellpadding - richtext->height() ; + return parent->cellpadding; +} + +void Q3TextTableCell::draw(QPainter* p, int x, int y, int cx, int cy, int cw, int ch, + const QPalette &pal, bool) +{ + if (cached_width != geom.width()) { + int extra = 2 * (parent->innerborder + parent->cellpadding); + richtext->doLayout(p, geom.width() - extra); + cached_width = geom.width(); + } + QPalette pal2(pal); + if (background) + pal2.setBrush(QPalette::Base, *background); + else if (richtext->paper()) + pal2.setBrush(QPalette::Base, *richtext->paper()); + + p->save(); + p->translate(x + geom.x(), y + geom.y()); + if (background) + p->fillRect(0, 0, geom.width(), geom.height(), *background); + else if (richtext->paper()) + p->fillRect(0, 0, geom.width(), geom.height(), *richtext->paper()); + + p->translate(horizontalAlignmentOffset(), verticalAlignmentOffset()); + + QRegion r; + if (cx >= 0 && cy >= 0) + richtext->draw(p, cx - (x + horizontalAlignmentOffset() + geom.x()), + cy - (y + geom.y() + verticalAlignmentOffset()), + cw, ch, pal2, false, false, 0); + else + richtext->draw(p, -1, -1, -1, -1, pal2, false, false, 0); + + p->restore(); +} +#endif + +QT_END_NAMESPACE + +#endif //QT_NO_RICHTEXT diff --git a/src/qt3support/text/q3richtext_p.cpp b/src/qt3support/text/q3richtext_p.cpp new file mode 100644 index 0000000..6249f1b --- /dev/null +++ b/src/qt3support/text/q3richtext_p.cpp @@ -0,0 +1,636 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3richtext_p.h" + +#ifndef QT_NO_RICHTEXT + +QT_BEGIN_NAMESPACE + +Q3TextCommand::~Q3TextCommand() {} +Q3TextCommand::Commands Q3TextCommand::type() const { return Invalid; } + + +#ifndef QT_NO_TEXTCUSTOMITEM +Q3TextCustomItem::~Q3TextCustomItem() {} +void Q3TextCustomItem::adjustToPainter(QPainter* p){ if (p) width = 0; } +Q3TextCustomItem::Placement Q3TextCustomItem::placement() const { return PlaceInline; } + +bool Q3TextCustomItem::ownLine() const { return false; } +void Q3TextCustomItem::resize(int nwidth){ width = nwidth; } +void Q3TextCustomItem::invalidate() {} + +bool Q3TextCustomItem::isNested() const { return false; } +int Q3TextCustomItem::minimumWidth() const { return 0; } + +QString Q3TextCustomItem::richText() const { return QString(); } + +bool Q3TextCustomItem::enter(Q3TextCursor *, Q3TextDocument*&, Q3TextParagraph *&, int &, int &, int &, bool) +{ + return true; +} +bool Q3TextCustomItem::enterAt(Q3TextCursor *, Q3TextDocument *&, Q3TextParagraph *&, int &, int &, int &, const QPoint &) +{ + return true; +} +bool Q3TextCustomItem::next(Q3TextCursor *, Q3TextDocument *&, Q3TextParagraph *&, int &, int &, int &) +{ + return true; +} +bool Q3TextCustomItem::prev(Q3TextCursor *, Q3TextDocument *&, Q3TextParagraph *&, int &, int &, int &) +{ + return true; +} +bool Q3TextCustomItem::down(Q3TextCursor *, Q3TextDocument *&, Q3TextParagraph *&, int &, int &, int &) +{ + return true; +} +bool Q3TextCustomItem::up(Q3TextCursor *, Q3TextDocument *&, Q3TextParagraph *&, int &, int &, int &) +{ + return true; +} +#endif // QT_NO_TEXTCUSTOMITEM + +void Q3TextFlow::setPageSize(int ps) { pagesize = ps; } +#ifndef QT_NO_TEXTCUSTOMITEM +bool Q3TextFlow::isEmpty() { return leftItems.isEmpty() && rightItems.isEmpty(); } +#else +bool Q3TextFlow::isEmpty() { return true; } +#endif + +#ifndef QT_NO_TEXTCUSTOMITEM +void Q3TextTableCell::invalidate() { cached_width = -1; cached_sizehint = -1; } + +void Q3TextTable::invalidate() { cachewidth = -1; } +#endif + +Q3TextParagraphData::~Q3TextParagraphData() {} +void Q3TextParagraphData::join(Q3TextParagraphData *) {} + +Q3TextFormatter::~Q3TextFormatter() {} +void Q3TextFormatter::setWrapEnabled(bool b) { wrapEnabled = b; } +void Q3TextFormatter::setWrapAtColumn(int c) { wrapColumn = c; } + + + +int Q3TextCursor::x() const +{ + if (idx >= para->length()) + return 0; + Q3TextStringChar *c = para->at(idx); + int curx = c->x; + if (!c->rightToLeft && + c->c.isSpace() && + idx > 0 && + para->at(idx - 1)->c != QLatin1Char('\t') && + !c->lineStart && + (para->alignment() & Qt::AlignJustify) == Qt::AlignJustify) + curx = para->at(idx - 1)->x + para->string()->width(idx - 1); + if (c->rightToLeft) + curx += para->string()->width(idx); + return curx; +} + +int Q3TextCursor::y() const +{ + int dummy, line; + para->lineStartOfChar(idx, &dummy, &line); + return para->lineY(line); +} + +int Q3TextCursor::globalX() const { return totalOffsetX() + para->rect().x() + x(); } +int Q3TextCursor::globalY() const { return totalOffsetY() + para->rect().y() + y(); } + +Q3TextDocument *Q3TextCursor::document() const +{ + return para ? para->document() : 0; +} + +void Q3TextCursor::gotoPosition(Q3TextParagraph* p, int index) +{ + if (para && p != para) { + while (!indices.isEmpty() && para->document() != p->document()) + pop(); + Q_ASSERT(indices.isEmpty() || para->document() == p->document()); + } + para = p; + if (index < 0 || index >= para->length()) { + qWarning("Q3TextCursor::gotoParagraph Index: %d out of range", index); + if (index < 0 || para->length() == 0) + index = 0; + else + index = para->length() - 1; + } + + tmpX = -1; + idx = index; + fixCursorPosition(); +} + +bool Q3TextDocument::hasSelection(int id, bool visible) const +{ + return (selections.find(id) != selections.end() && + (!visible || + ((Q3TextDocument*)this)->selectionStartCursor(id) != + ((Q3TextDocument*)this)->selectionEndCursor(id))); +} + +void Q3TextDocument::setSelectionStart(int id, const Q3TextCursor &cursor) +{ + Q3TextDocumentSelection sel; + sel.startCursor = cursor; + sel.endCursor = cursor; + sel.swapped = false; + selections[id] = sel; +} + +Q3TextParagraph *Q3TextDocument::paragAt(int i) const +{ + Q3TextParagraph* p = curParag; + if (!p || p->paragId() > i) + p = fParag; + while (p && p->paragId() != i) + p = p->next(); + ((Q3TextDocument*)this)->curParag = p; + return p; +} + + +Q3TextFormat::~Q3TextFormat() +{ +} + +Q3TextFormat::Q3TextFormat() + : fm(QFontMetrics(fn)), linkColor(true), logicalFontSize(3), stdSize(qApp->font().pointSize()) +{ + ref = 0; + + usePixelSizes = false; + if (stdSize == -1) { + stdSize = qApp->font().pixelSize(); + usePixelSizes = true; + } + + missp = false; + ha = AlignNormal; + collection = 0; +} + +Q3TextFormat::Q3TextFormat(const Q3StyleSheetItem *style) + : fm(QFontMetrics(fn)), linkColor(true), logicalFontSize(3), stdSize(qApp->font().pointSize()) +{ + ref = 0; + + usePixelSizes = false; + if (stdSize == -1) { + stdSize = qApp->font().pixelSize(); + usePixelSizes = true; + } + + missp = false; + ha = AlignNormal; + collection = 0; + fn = QFont(style->fontFamily(), + style->fontSize(), + style->fontWeight(), + style->fontItalic()); + fn.setUnderline(style->fontUnderline()); + fn.setStrikeOut(style->fontStrikeOut()); + col = style->color(); + fm = QFontMetrics(fn); + leftBearing = fm.minLeftBearing(); + rightBearing = fm.minRightBearing(); + hei = fm.lineSpacing(); + asc = fm.ascent() + (fm.leading()+1)/2; + dsc = fm.descent(); + missp = false; + ha = AlignNormal; + memset(widths, 0, 256); + generateKey(); + addRef(); +} + +Q3TextFormat::Q3TextFormat(const QFont &f, const QColor &c, Q3TextFormatCollection *parent) + : fn(f), col(c), fm(QFontMetrics(f)), linkColor(true), + logicalFontSize(3), stdSize(f.pointSize()) +{ + ref = 0; + usePixelSizes = false; + if (stdSize == -1) { + stdSize = f.pixelSize(); + usePixelSizes = true; + } + collection = parent; + leftBearing = fm.minLeftBearing(); + rightBearing = fm.minRightBearing(); + hei = fm.lineSpacing(); + asc = fm.ascent() + (fm.leading()+1)/2; + dsc = fm.descent(); + missp = false; + ha = AlignNormal; + memset(widths, 0, 256); + generateKey(); + addRef(); +} + +Q3TextFormat::Q3TextFormat(const Q3TextFormat &f) + : fm(f.fm) +{ + ref = 0; + collection = 0; + fn = f.fn; + col = f.col; + leftBearing = f.leftBearing; + rightBearing = f.rightBearing; + memset(widths, 0, 256); + hei = f.hei; + asc = f.asc; + dsc = f.dsc; + stdSize = f.stdSize; + usePixelSizes = f.usePixelSizes; + logicalFontSize = f.logicalFontSize; + missp = f.missp; + ha = f.ha; + k = f.k; + linkColor = f.linkColor; + addRef(); +} + +Q3TextFormat& Q3TextFormat::operator=(const Q3TextFormat &f) +{ + ref = 0; + collection = f.collection; + fn = f.fn; + col = f.col; + fm = f.fm; + leftBearing = f.leftBearing; + rightBearing = f.rightBearing; + memset(widths, 0, 256); + hei = f.hei; + asc = f.asc; + dsc = f.dsc; + stdSize = f.stdSize; + usePixelSizes = f.usePixelSizes; + logicalFontSize = f.logicalFontSize; + missp = f.missp; + ha = f.ha; + k = f.k; + linkColor = f.linkColor; + addRef(); + return *this; +} + +void Q3TextFormat::update() +{ + fm = QFontMetrics(fn); + leftBearing = fm.minLeftBearing(); + rightBearing = fm.minRightBearing(); + hei = fm.lineSpacing(); + asc = fm.ascent() + (fm.leading()+1)/2; + dsc = fm.descent(); + memset(widths, 0, 256); + generateKey(); +} + + +QPainter* Q3TextFormat::pntr = 0; +QFontMetrics* Q3TextFormat::pntr_fm = 0; +int Q3TextFormat::pntr_ldg=-1; +int Q3TextFormat::pntr_asc=-1; +int Q3TextFormat::pntr_hei=-1; +int Q3TextFormat::pntr_dsc=-1; + +void Q3TextFormat::setPainter(QPainter *p) +{ + pntr = p; +} + +QPainter* Q3TextFormat::painter() +{ + return pntr; +} + +void Q3TextFormat::applyFont(const QFont &f) +{ + QFontMetrics fm(pntr->fontMetrics()); + if (!pntr_fm || pntr->font() != f) { + pntr->setFont(f); + delete pntr_fm; + pntr_fm = new QFontMetrics(pntr->fontMetrics()); + pntr_ldg = pntr_fm->leading(); + pntr_asc = pntr_fm->ascent()+(pntr_ldg+1)/2; + pntr_hei = pntr_fm->lineSpacing(); + pntr_dsc = -1; + } +} + +int Q3TextFormat::minLeftBearing() const +{ + if (!pntr || !pntr->isActive()) + return leftBearing; + applyFont(fn); + return pntr_fm->minLeftBearing(); +} + +int Q3TextFormat::minRightBearing() const +{ + if (!pntr || !pntr->isActive()) + return rightBearing; + applyFont(fn); + return pntr_fm->minRightBearing(); +} + +int Q3TextFormat::height() const +{ + if (!pntr || !pntr->isActive()) + return hei; + applyFont(fn); + return pntr_hei; +} + +int Q3TextFormat::ascent() const +{ + if (!pntr || !pntr->isActive()) + return asc; + applyFont(fn); + return pntr_asc; +} + +int Q3TextFormat::descent() const +{ + if (!pntr || !pntr->isActive()) + return dsc; + applyFont(fn); + if (pntr_dsc < 0) + pntr_dsc = pntr_fm->descent(); + return pntr_dsc; +} + +int Q3TextFormat::leading() const +{ + if (!pntr || !pntr->isActive()) + return fm.leading(); + applyFont(fn); + return pntr_ldg; +} + +void Q3TextFormat::generateKey() +{ + k = getKey(fn, col, isMisspelled(), vAlign()); +} + +QString Q3TextFormat::getKey(const QFont &fn, const QColor &col, bool misspelled, VerticalAlignment a) +{ + QString k = fn.key(); + k += QLatin1Char('/'); + k += QString::number((uint)col.rgb()); + k += QLatin1Char('/'); + k += QString::number((int)misspelled); + k += QLatin1Char('/'); + k += QString::number((int)a); + return k; +} + +QString Q3TextString::toString(const QVector<Q3TextStringChar> &data) +{ + QString s; + int l = data.size(); + s.setUnicode(0, l); + const Q3TextStringChar *c = data.data(); + QChar *uc = (QChar *)s.unicode(); + while (l--) + *(uc++) = (c++)->c; + + return s; +} + +void Q3TextParagraph::setSelection(int id, int start, int end) +{ + QMap<int, Q3TextParagraphSelection>::ConstIterator it = selections().constFind(id); + if (it != mSelections->constEnd()) { + if (start == (*it).start && end == (*it).end) + return; + } + + Q3TextParagraphSelection sel; + sel.start = start; + sel.end = end; + (*mSelections)[id] = sel; + setChanged(true, true); +} + +void Q3TextParagraph::removeSelection(int id) +{ + if (!hasSelection(id)) + return; + if (mSelections) + mSelections->remove(id); + setChanged(true, true); +} + +int Q3TextParagraph::selectionStart(int id) const +{ + if (!mSelections) + return -1; + QMap<int, Q3TextParagraphSelection>::ConstIterator it = mSelections->constFind(id); + if (it == mSelections->constEnd()) + return -1; + return (*it).start; +} + +int Q3TextParagraph::selectionEnd(int id) const +{ + if (!mSelections) + return -1; + QMap<int, Q3TextParagraphSelection>::ConstIterator it = mSelections->constFind(id); + if (it == mSelections->constEnd()) + return -1; + return (*it).end; +} + +bool Q3TextParagraph::hasSelection(int id) const +{ + return mSelections ? mSelections->contains(id) : false; +} + +bool Q3TextParagraph::fullSelected(int id) const +{ + if (!mSelections) + return false; + QMap<int, Q3TextParagraphSelection>::ConstIterator it = mSelections->constFind(id); + if (it == mSelections->constEnd()) + return false; + return (*it).start == 0 && (*it).end == str->length() - 1; +} + +int Q3TextParagraph::lineY(int l) const +{ + if (l > (int)lineStarts.count() - 1) { + qWarning("Q3TextParagraph::lineY: line %d out of range!", l); + return 0; + } + + if (!isValid()) + ((Q3TextParagraph*)this)->format(); + + QMap<int, QTextLineStart*>::ConstIterator it = lineStarts.begin(); + while (l-- > 0) + ++it; + return (*it)->y; +} + +int Q3TextParagraph::lineBaseLine(int l) const +{ + if (l > (int)lineStarts.count() - 1) { + qWarning("Q3TextParagraph::lineBaseLine: line %d out of range!", l); + return 10; + } + + if (!isValid()) + ((Q3TextParagraph*)this)->format(); + + QMap<int, QTextLineStart*>::ConstIterator it = lineStarts.begin(); + while (l-- > 0) + ++it; + return (*it)->baseLine; +} + +int Q3TextParagraph::lineHeight(int l) const +{ + if (l > (int)lineStarts.count() - 1) { + qWarning("Q3TextParagraph::lineHeight: line %d out of range!", l); + return 15; + } + + if (!isValid()) + ((Q3TextParagraph*)this)->format(); + + QMap<int, QTextLineStart*>::ConstIterator it = lineStarts.begin(); + while (l-- > 0) + ++it; + return (*it)->h; +} + +void Q3TextParagraph::lineInfo(int l, int &y, int &h, int &bl) const +{ + if (l > (int)lineStarts.count() - 1) { + qWarning("Q3TextParagraph::lineInfo: line %d out of range!", l); + qDebug("%d %d", (int)lineStarts.count() - 1, l); + y = 0; + h = 15; + bl = 10; + return; + } + + if (!isValid()) + ((Q3TextParagraph*)this)->format(); + + QMap<int, QTextLineStart*>::ConstIterator it = lineStarts.begin(); + while (l-- > 0) + ++it; + y = (*it)->y; + h = (*it)->h; + bl = (*it)->baseLine; +} + + +void Q3TextParagraph::setAlignment(int a) +{ + if (a == (int)align) + return; + align = a; + invalidate(0); +} + +Q3TextFormatter *Q3TextParagraph::formatter() const +{ + if (hasdoc) + return document()->formatter(); + if (pseudoDocument()->pFormatter) + return pseudoDocument()->pFormatter; + return (((Q3TextParagraph*)this)->pseudoDocument()->pFormatter = new Q3TextFormatterBreakWords); +} + +void Q3TextParagraph::setTabArray(int *a) +{ + delete [] tArray; + tArray = a; +} + +void Q3TextParagraph::setTabStops(int tw) +{ + if (hasdoc) + document()->setTabStops(tw); + else + tabStopWidth = tw; +} + +QMap<int, Q3TextParagraphSelection> &Q3TextParagraph::selections() const +{ + if (!mSelections) + ((Q3TextParagraph *)this)->mSelections = new QMap<int, Q3TextParagraphSelection>; + return *mSelections; +} + +#ifndef QT_NO_TEXTCUSTOMITEM +QList<Q3TextCustomItem *> &Q3TextParagraph::floatingItems() const +{ + if (!mFloatingItems) + ((Q3TextParagraph *)this)->mFloatingItems = new QList<Q3TextCustomItem *>; + return *mFloatingItems; +} +#endif + +Q3TextStringChar::~Q3TextStringChar() +{ + if (format()) + format()->removeRef(); + if (type) // not Regular + delete p.custom; +} + +Q3TextParagraphPseudoDocument::Q3TextParagraphPseudoDocument():pFormatter(0),commandHistory(0), minw(0),wused(0),collection(){} +Q3TextParagraphPseudoDocument::~Q3TextParagraphPseudoDocument(){ delete pFormatter; delete commandHistory; } + + +QT_END_NAMESPACE + +#endif //QT_NO_RICHTEXT diff --git a/src/qt3support/text/q3richtext_p.h b/src/qt3support/text/q3richtext_p.h new file mode 100644 index 0000000..2248e52 --- /dev/null +++ b/src/qt3support/text/q3richtext_p.h @@ -0,0 +1,2102 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3RICHTEXT_P_H +#define Q3RICHTEXT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of a number of Qt sources files. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "QtGui/qapplication.h" +#include "QtGui/qcolor.h" +#include "QtCore/qhash.h" +#include "QtGui/qfont.h" +#include "QtGui/qfontmetrics.h" +#include "QtGui/qlayout.h" +#include "QtCore/qmap.h" +#include "QtCore/qvector.h" +#include "QtCore/qstack.h" +#include "QtCore/qlist.h" +#include "QtCore/qobject.h" +#include "QtGui/qpainter.h" +#include "QtGui/qpixmap.h" +#include "QtCore/qrect.h" +#include "QtCore/qsize.h" +#include "QtCore/qstring.h" +#include "QtCore/qstringlist.h" +#include "Qt3Support/q3stylesheet.h" +#include "Qt3Support/q3mimefactory.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_RICHTEXT + +class Q3TextDocument; +class Q3TextString; +class Q3TextPreProcessor; +class Q3TextFormat; +class Q3TextCursor; +class Q3TextParagraph; +class Q3TextFormatter; +class Q3TextIndent; +class Q3TextFormatCollection; +class Q3StyleSheetItem; +#ifndef QT_NO_TEXTCUSTOMITEM +class Q3TextCustomItem; +#endif +class Q3TextFlow; +struct QBidiContext; + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_COMPAT_EXPORT Q3TextStringChar +{ + friend class Q3TextString; + +public: + // this is never called, initialize variables in Q3TextString::insert()!!! + Q3TextStringChar() : nobreak(false), lineStart(0), type(Regular) {p.format=0;} + ~Q3TextStringChar(); + + struct CustomData + { + Q3TextFormat *format; +#ifndef QT_NO_TEXTCUSTOMITEM + Q3TextCustomItem *custom; +#endif + QString anchorName; + QString anchorHref; + }; + enum Type { Regular=0, Custom=1, Anchor=2, CustomAnchor=3 }; + + QChar c; + // this is the same struct as in qtextengine_p.h. Don't change! + uchar softBreak :1; // Potential linebreak point + uchar whiteSpace :1; // A unicode whitespace character, except NBSP, ZWNBSP + uchar charStop :1; // Valid cursor position (for left/right arrow) + uchar nobreak :1; + + uchar lineStart : 1; + uchar /*Type*/ type : 2; + uchar bidiLevel :7; + uchar rightToLeft : 1; + + int x; + union { + Q3TextFormat* format; + CustomData* custom; + } p; + + + int height() const; + int ascent() const; + int descent() const; + bool isCustom() const { return (type & Custom) != 0; } + Q3TextFormat *format() const; +#ifndef QT_NO_TEXTCUSTOMITEM + Q3TextCustomItem *customItem() const; +#endif + void setFormat(Q3TextFormat *f); +#ifndef QT_NO_TEXTCUSTOMITEM + void setCustomItem(Q3TextCustomItem *i); +#endif + +#ifndef QT_NO_TEXTCUSTOMITEM + void loseCustomItem(); +#endif + + + bool isAnchor() const { return (type & Anchor) != 0; } + bool isLink() const { return isAnchor() && p.custom->anchorHref.count(); } + QString anchorName() const; + QString anchorHref() const; + void setAnchor(const QString& name, const QString& href); + + Q3TextStringChar(const Q3TextStringChar &) { + Q_ASSERT(false); + } +private: + Q3TextStringChar &operator=(const Q3TextStringChar &) { + //abort(); + return *this; + } + friend class Q3TextParagraph; +}; + +Q_DECLARE_TYPEINFO(Q3TextStringChar, Q_PRIMITIVE_TYPE); + +class Q_COMPAT_EXPORT Q3TextString +{ +public: + + Q3TextString(); + Q3TextString(const Q3TextString &s); + virtual ~Q3TextString(); + + static QString toString(const QVector<Q3TextStringChar> &data); + QString toString() const; + + inline Q3TextStringChar &at(int i) const { + return const_cast<Q3TextString *>(this)->data[i]; + } + inline int length() const { return data.size(); } + + int width(int idx) const; + + void insert(int index, const QString &s, Q3TextFormat *f); + void insert(int index, const QChar *unicode, int len, Q3TextFormat *f); + void insert(int index, Q3TextStringChar *c, bool doAddRefFormat = false); + void truncate(int index); + void remove(int index, int len); + void clear(); + + void setFormat(int index, Q3TextFormat *f, bool useCollection); + + void setBidi(bool b) { bidi = b; } + bool isBidi() const; + bool isRightToLeft() const; + QChar::Direction direction() const; + void setDirection(QChar::Direction dr) { dir = dr; bidiDirty = true; } + + QVector<Q3TextStringChar> rawData() const { return data; } + + void operator=(const QString &s) { clear(); insert(0, s, 0); } + void operator+=(const QString &s) { insert(length(), s, 0); } + void prepend(const QString &s) { insert(0, s, 0); } + int appendParagraphs( Q3TextParagraph *start, Q3TextParagraph *end ); + + // return next and previous valid cursor positions. + bool validCursorPosition(int idx); + int nextCursorPosition(int idx); + int previousCursorPosition(int idx); + +private: + void checkBidi() const; + + QVector<Q3TextStringChar> data; + QString stringCache; + uint bidiDirty : 1; + uint bidi : 1; // true when the paragraph has right to left characters + uint rightToLeft : 1; + uint dir : 5; +}; + +inline bool Q3TextString::isBidi() const +{ + if (bidiDirty) + checkBidi(); + return bidi; +} + +inline bool Q3TextString::isRightToLeft() const +{ + if (bidiDirty) + checkBidi(); + return rightToLeft; +} + +inline QString Q3TextString::toString() const +{ + if (bidiDirty) + checkBidi(); + return stringCache; +} + +inline QChar::Direction Q3TextString::direction() const +{ + return rightToLeft ? QChar::DirR : QChar::DirL; +} + +inline int Q3TextString::nextCursorPosition(int next) +{ + if (bidiDirty) + checkBidi(); + + const Q3TextStringChar *c = data.data(); + int len = length(); + + if (next < len - 1) { + next++; + while (next < len - 1 && !c[next].charStop) + next++; + } + return next; +} + +inline int Q3TextString::previousCursorPosition(int prev) +{ + if (bidiDirty) + checkBidi(); + + const Q3TextStringChar *c = data.data(); + + if (prev) { + prev--; + while (prev && !c[prev].charStop) + prev--; + } + return prev; +} + +inline bool Q3TextString::validCursorPosition(int idx) +{ + if (bidiDirty) + checkBidi(); + + return (at(idx).charStop); +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_COMPAT_EXPORT Q3TextCursor +{ +public: + Q3TextCursor(Q3TextDocument * = 0); + Q3TextCursor(const Q3TextCursor &c); + Q3TextCursor &operator=(const Q3TextCursor &c); + virtual ~Q3TextCursor(); + + bool operator==(const Q3TextCursor &c) const; + bool operator!=(const Q3TextCursor &c) const { return !(*this == c); } + + inline Q3TextParagraph *paragraph() const { return para; } + + Q3TextDocument *document() const; + int index() const; + + void gotoPosition(Q3TextParagraph* p, int index = 0); + void setIndex(int index) { gotoPosition(paragraph(), index); } + void setParagraph(Q3TextParagraph*p) { gotoPosition(p, 0); } + + void gotoLeft(); + void gotoRight(); + void gotoNextLetter(); + void gotoPreviousLetter(); + void gotoUp(); + void gotoDown(); + void gotoLineEnd(); + void gotoLineStart(); + void gotoHome(); + void gotoEnd(); + void gotoPageUp(int visibleHeight); + void gotoPageDown(int visibleHeight); + void gotoNextWord(bool onlySpace = false); + void gotoPreviousWord(bool onlySpace = false); + void gotoWordLeft(); + void gotoWordRight(); + + void insert(const QString &s, bool checkNewLine, QVector<Q3TextStringChar> *formatting = 0); + void splitAndInsertEmptyParagraph(bool ind = true, bool updateIds = true); + bool remove(); + bool removePreviousChar(); + void indent(); + + bool atParagStart(); + bool atParagEnd(); + + int x() const; // x in current paragraph + int y() const; // y in current paragraph + + int globalX() const; + int globalY() const; + + Q3TextParagraph *topParagraph() const { return paras.isEmpty() ? para : paras.first(); } + int offsetX() const { return ox; } // inner document offset + int offsetY() const { return oy; } // inner document offset + int totalOffsetX() const; // total document offset + int totalOffsetY() const; // total document offset + + bool place(const QPoint &pos, Q3TextParagraph *s) { return place(pos, s, false); } + bool place(const QPoint &pos, Q3TextParagraph *s, bool link); + void restoreState(); + + + int nestedDepth() const { return (int)indices.count(); } //### size_t/int cast + void oneUp() { if (!indices.isEmpty()) pop(); } + void setValid(bool b) { valid = b; } + bool isValid() const { return valid; } + + void fixCursorPosition(); +private: + enum Operation { EnterBegin, EnterEnd, Next, Prev, Up, Down }; + + void push(); + void pop(); + bool processNesting(Operation op); + void invalidateNested(); + void gotoIntoNested(const QPoint &globalPos); + + Q3TextParagraph *para; + int idx, tmpX; + int ox, oy; + QStack<int> indices; + QStack<Q3TextParagraph*> paras; + QStack<int> xOffsets; + QStack<int> yOffsets; + uint valid : 1; + +}; + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_COMPAT_EXPORT Q3TextCommand +{ +public: + enum Commands { Invalid, Insert, Delete, Format, Style }; + + Q3TextCommand(Q3TextDocument *dc) : doc(dc), cursor(dc) {} + virtual ~Q3TextCommand(); + + virtual Commands type() const; + + virtual Q3TextCursor *execute(Q3TextCursor *c) = 0; + virtual Q3TextCursor *unexecute(Q3TextCursor *c) = 0; + +protected: + Q3TextDocument *doc; + Q3TextCursor cursor; + +}; + +class Q_COMPAT_EXPORT Q3TextCommandHistory +{ +public: + Q3TextCommandHistory(int s) : current(-1), steps(s) { } + virtual ~Q3TextCommandHistory(); // ### why is it virtual? + + void clear(); + + void addCommand(Q3TextCommand *cmd); + Q3TextCursor *undo(Q3TextCursor *c); + Q3TextCursor *redo(Q3TextCursor *c); + + bool isUndoAvailable(); + bool isRedoAvailable(); + + void setUndoDepth(int depth) { steps = depth; } + int undoDepth() const { return steps; } + + int historySize() const { return history.count(); } + int currentPosition() const { return current; } + +private: + QList<Q3TextCommand *> history; + int current, steps; +}; + +inline Q3TextCommandHistory::~Q3TextCommandHistory() +{ + clear(); +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +#ifndef QT_NO_TEXTCUSTOMITEM +class Q_COMPAT_EXPORT Q3TextCustomItem +{ +public: + Q3TextCustomItem(Q3TextDocument *p) + : xpos(0), ypos(-1), width(-1), height(0), parent(p) + {} + virtual ~Q3TextCustomItem(); + virtual void draw(QPainter* p, int x, int y, int cx, int cy, int cw, int ch, + const QPalette &pal, bool selected) = 0; + + virtual void adjustToPainter(QPainter*); + + enum Placement { PlaceInline = 0, PlaceLeft, PlaceRight }; + virtual Placement placement() const; + bool placeInline() { return placement() == PlaceInline; } + + virtual bool ownLine() const; + virtual void resize(int nwidth); + virtual void invalidate(); + virtual int ascent() const { return height; } + + virtual bool isNested() const; + virtual int minimumWidth() const; + + virtual QString richText() const; + + int xpos; // used for floating items + int ypos; // used for floating items + int width; + int height; + + QRect geometry() const { return QRect(xpos, ypos, width, height); } + + virtual bool enter(Q3TextCursor *, Q3TextDocument *&doc, Q3TextParagraph *¶g, int &idx, int &ox, int &oy, bool atEnd = false); + virtual bool enterAt(Q3TextCursor *, Q3TextDocument *&doc, Q3TextParagraph *¶g, int &idx, int &ox, int &oy, const QPoint &); + virtual bool next(Q3TextCursor *, Q3TextDocument *&doc, Q3TextParagraph *¶g, int &idx, int &ox, int &oy); + virtual bool prev(Q3TextCursor *, Q3TextDocument *&doc, Q3TextParagraph *¶g, int &idx, int &ox, int &oy); + virtual bool down(Q3TextCursor *, Q3TextDocument *&doc, Q3TextParagraph *¶g, int &idx, int &ox, int &oy); + virtual bool up(Q3TextCursor *, Q3TextDocument *&doc, Q3TextParagraph *¶g, int &idx, int &ox, int &oy); + + void setParagraph(Q3TextParagraph *p) { parag = p; } + Q3TextParagraph *paragraph() const { return parag; } + + Q3TextDocument *parent; + Q3TextParagraph *parag; + + virtual void pageBreak(int y, Q3TextFlow* flow); +}; +#endif + + +#ifndef QT_NO_TEXTCUSTOMITEM +class Q_COMPAT_EXPORT Q3TextImage : public Q3TextCustomItem +{ +public: + Q3TextImage(Q3TextDocument *p, const QMap<QString, QString> &attr, const QString& context, + Q3MimeSourceFactory &factory); + virtual ~Q3TextImage(); + + Placement placement() const { return place; } + void adjustToPainter(QPainter*); + int minimumWidth() const { return width; } + + QString richText() const; + + void draw(QPainter* p, int x, int y, int cx, int cy, int cw, int ch, + const QPalette &pal, bool selected); + +private: + QRegion* reg; + QPixmap pm; + Placement place; + int tmpwidth, tmpheight; + QMap<QString, QString> attributes; + QString imgId; + +}; +#endif + +#ifndef QT_NO_TEXTCUSTOMITEM +class Q_COMPAT_EXPORT Q3TextHorizontalLine : public Q3TextCustomItem +{ +public: + Q3TextHorizontalLine(Q3TextDocument *p, const QMap<QString, QString> &attr, const QString& context, + Q3MimeSourceFactory &factory); + virtual ~Q3TextHorizontalLine(); + + void adjustToPainter(QPainter*); + void draw(QPainter* p, int x, int y, int cx, int cy, int cw, int ch, + const QPalette &pal, bool selected); + QString richText() const; + + bool ownLine() const { return true; } + +private: + int tmpheight; + QColor color; + bool shade; + +}; +#endif + +class Q_COMPAT_EXPORT Q3TextFlow +{ + friend class Q3TextDocument; +#ifndef QT_NO_TEXTCUSTOMITEM + friend class Q3TextTableCell; +#endif + +public: + Q3TextFlow(); + virtual ~Q3TextFlow(); + + virtual void setWidth(int width); + int width() const; + + virtual void setPageSize(int ps); + int pageSize() const { return pagesize; } + + virtual int adjustLMargin(int yp, int h, int margin, int space); + virtual int adjustRMargin(int yp, int h, int margin, int space); + +#ifndef QT_NO_TEXTCUSTOMITEM + virtual void registerFloatingItem(Q3TextCustomItem* item); + virtual void unregisterFloatingItem(Q3TextCustomItem* item); +#endif + virtual QRect boundingRect() const; + virtual void drawFloatingItems(QPainter* p, int cx, int cy, int cw, int ch, + const QPalette &pal, bool selected); + + virtual int adjustFlow(int y, int w, int h); // adjusts y according to the defined pagesize. Returns the shift. + + virtual bool isEmpty(); + + void clear(); + +private: + int w; + int pagesize; + +#ifndef QT_NO_TEXTCUSTOMITEM + QList<Q3TextCustomItem *> leftItems; + QList<Q3TextCustomItem *> rightItems; +#endif +}; + +inline int Q3TextFlow::width() const { return w; } + +#ifndef QT_NO_TEXTCUSTOMITEM +class Q3TextTable; + +class Q_COMPAT_EXPORT Q3TextTableCell : public QLayoutItem +{ + friend class Q3TextTable; + +public: + Q3TextTableCell(Q3TextTable* table, + int row, int column, + const QMap<QString, QString> &attr, + const Q3StyleSheetItem* style, + const Q3TextFormat& fmt, const QString& context, + Q3MimeSourceFactory &factory, Q3StyleSheet *sheet, const QString& doc); + virtual ~Q3TextTableCell(); + + QSize sizeHint() const ; + QSize minimumSize() const ; + QSize maximumSize() const ; + Qt::Orientations expandingDirections() const; + bool isEmpty() const; + void setGeometry(const QRect&) ; + QRect geometry() const; + + bool hasHeightForWidth() const; + int heightForWidth(int) const; + + void adjustToPainter(QPainter*); + + int row() const { return row_; } + int column() const { return col_; } + int rowspan() const { return rowspan_; } + int colspan() const { return colspan_; } + int stretch() const { return stretch_; } + + Q3TextDocument* richText() const { return richtext; } + Q3TextTable* table() const { return parent; } + + void draw(QPainter* p, int x, int y, int cx, int cy, int cw, int ch, + const QPalette &cg, bool selected); + + QBrush *backGround() const { return background; } + virtual void invalidate(); + + int verticalAlignmentOffset() const; + int horizontalAlignmentOffset() const; + +private: + QRect geom; + Q3TextTable* parent; + Q3TextDocument* richtext; + int row_; + int col_; + int rowspan_; + int colspan_; + int stretch_; + int maxw; + int minw; + bool hasFixedWidth; + QBrush *background; + int cached_width; + int cached_sizehint; + QMap<QString, QString> attributes; + int align; +}; +#endif + + +#ifndef QT_NO_TEXTCUSTOMITEM +class Q_COMPAT_EXPORT Q3TextTable: public Q3TextCustomItem +{ + friend class Q3TextTableCell; + +public: + Q3TextTable(Q3TextDocument *p, const QMap<QString, QString> &attr); + virtual ~Q3TextTable(); + + void adjustToPainter(QPainter *p); + void pageBreak(int y, Q3TextFlow* flow); + void draw(QPainter* p, int x, int y, int cx, int cy, int cw, int ch, + const QPalette &pal, bool selected); + + bool noErase() const { return true; } + bool ownLine() const { return true; } + Placement placement() const { return place; } + bool isNested() const { return true; } + void resize(int nwidth); + virtual void invalidate(); + + virtual bool enter(Q3TextCursor *c, Q3TextDocument *&doc, Q3TextParagraph *¶g, int &idx, int &ox, int &oy, bool atEnd = false); + virtual bool enterAt(Q3TextCursor *c, Q3TextDocument *&doc, Q3TextParagraph *¶g, int &idx, int &ox, int &oy, const QPoint &pos); + virtual bool next(Q3TextCursor *c, Q3TextDocument *&doc, Q3TextParagraph *¶g, int &idx, int &ox, int &oy); + virtual bool prev(Q3TextCursor *c, Q3TextDocument *&doc, Q3TextParagraph *¶g, int &idx, int &ox, int &oy); + virtual bool down(Q3TextCursor *c, Q3TextDocument *&doc, Q3TextParagraph *¶g, int &idx, int &ox, int &oy); + virtual bool up(Q3TextCursor *c, Q3TextDocument *&doc, Q3TextParagraph *¶g, int &idx, int &ox, int &oy); + + QString richText() const; + + int minimumWidth() const; + + QList<Q3TextTableCell *> tableCells() const { return cells; } + + bool isStretching() const { return stretch; } + +private: + void format(int w); + void addCell(Q3TextTableCell* cell); + +private: + QGridLayout* layout; + QList<Q3TextTableCell *> cells; + int cachewidth; + int fixwidth; + int cellpadding; + int cellspacing; + int border; + int outerborder; + int stretch; + int innerborder; + int us_cp, us_ib, us_b, us_ob, us_cs; + int us_fixwidth; + QMap<QString, QString> attributes; + QMap<Q3TextCursor*, int> currCell; + Placement place; + void adjustCells(int y , int shift); + int pageBreakFor; +}; +#endif +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +#ifndef QT_NO_TEXTCUSTOMITEM +class Q3TextTableCell; +class Q3TextParagraph; +#endif + +struct Q_COMPAT_EXPORT Q3TextDocumentSelection +{ + Q3TextCursor startCursor, endCursor; + bool swapped; + Q_DUMMY_COMPARISON_OPERATOR(Q3TextDocumentSelection) +}; + +class Q_COMPAT_EXPORT Q3TextDocument : public QObject +{ + Q_OBJECT + +#ifndef QT_NO_TEXTCUSTOMITEM + friend class Q3TextTableCell; +#endif + friend class Q3TextCursor; + friend class Q3TextEdit; + friend class Q3TextParagraph; + +public: + enum SelectionIds { + Standard = 0, + Temp = 32000 // This selection must not be drawn, it's used e.g. by undo/redo to + // remove multiple lines with removeSelectedText() + }; + + Q3TextDocument(Q3TextDocument *p); + virtual ~Q3TextDocument(); + + Q3TextDocument *parent() const { return par; } + Q3TextParagraph *parentParagraph() const { return parentPar; } + + void setText(const QString &text, const QString &context); + QMap<QString, QString> attributes() const { return attribs; } + void setAttributes(const QMap<QString, QString> &attr) { attribs = attr; } + + QString text() const; + QString text(int parag) const; + QString originalText() const; + + int x() const; + int y() const; + int width() const; + int widthUsed() const; + int visibleWidth() const; + int height() const; + void setWidth(int w); + int minimumWidth() const; + bool setMinimumWidth(int needed, int used = -1, Q3TextParagraph *parag = 0); + + void setY(int y); + int leftMargin() const; + void setLeftMargin(int lm); + int rightMargin() const; + void setRightMargin(int rm); + + Q3TextParagraph *firstParagraph() const; + Q3TextParagraph *lastParagraph() const; + void setFirstParagraph(Q3TextParagraph *p); + void setLastParagraph(Q3TextParagraph *p); + + void invalidate(); + + void setPreProcessor(Q3TextPreProcessor *sh); + Q3TextPreProcessor *preProcessor() const; + + void setFormatter(Q3TextFormatter *f); + Q3TextFormatter *formatter() const; + + void setIndent(Q3TextIndent *i); + Q3TextIndent *indent() const; + + QColor selectionColor(int id) const; + QColor selectionTextColor(int id) const; + bool hasSelectionTextColor(int id) const; + void setSelectionColor(int id, const QColor &c); + void setSelectionTextColor(int id, const QColor &b); + bool hasSelection(int id, bool visible = false) const; + void setSelectionStart(int id, const Q3TextCursor &cursor); + bool setSelectionEnd(int id, const Q3TextCursor &cursor); + void selectAll(int id); + bool removeSelection(int id); + void selectionStart(int id, int ¶gId, int &index); + Q3TextCursor selectionStartCursor(int id); + Q3TextCursor selectionEndCursor(int id); + void selectionEnd(int id, int ¶gId, int &index); + void setFormat(int id, Q3TextFormat *f, int flags); + int numSelections() const { return nSelections; } + void addSelection(int id); + + QString selectedText(int id, bool asRichText = false) const; + void removeSelectedText(int id, Q3TextCursor *cursor); + void indentSelection(int id); + + Q3TextParagraph *paragAt(int i) const; + + void addCommand(Q3TextCommand *cmd); + Q3TextCursor *undo(Q3TextCursor *c = 0); + Q3TextCursor *redo(Q3TextCursor *c = 0); + Q3TextCommandHistory *commands() const { return commandHistory; } + + Q3TextFormatCollection *formatCollection() const; + + bool find(Q3TextCursor &cursor, const QString &expr, bool cs, bool wo, bool forward); + + void setTextFormat(Qt::TextFormat f); + Qt::TextFormat textFormat() const; + + bool inSelection(int selId, const QPoint &pos) const; + + Q3StyleSheet *styleSheet() const { return sheet_; } +#ifndef QT_NO_MIME + Q3MimeSourceFactory *mimeSourceFactory() const { return factory_; } +#endif + QString context() const { return contxt; } + + void setStyleSheet(Q3StyleSheet *s); + void setDefaultFormat(const QFont &font, const QColor &color); +#ifndef QT_NO_MIME + void setMimeSourceFactory(Q3MimeSourceFactory *f) { if (f) factory_ = f; } +#endif + void setContext(const QString &c) { if (!c.isEmpty()) contxt = c; } + + void setUnderlineLinks(bool b); + bool underlineLinks() const { return underlLinks; } + + void setPaper(QBrush *brush) { if (backBrush) delete backBrush; backBrush = brush; } + QBrush *paper() const { return backBrush; } + + void doLayout(QPainter *p, int w); + void draw(QPainter *p, const QRect& rect, const QPalette &pal, const QBrush *paper = 0); + + void drawParagraph(QPainter *p, Q3TextParagraph *parag, int cx, int cy, int cw, int ch, + QPixmap *&doubleBuffer, const QPalette &pal, + bool drawCursor, Q3TextCursor *cursor, bool resetChanged = true); + Q3TextParagraph *draw(QPainter *p, int cx, int cy, int cw, int ch, const QPalette &pal, + bool onlyChanged = false, bool drawCursor = false, Q3TextCursor *cursor = 0, + bool resetChanged = true); + +#ifndef QT_NO_TEXTCUSTOMITEM + static Q3TextCustomItem* tag(Q3StyleSheet *sheet, const QString& name, + const QMap<QString, QString> &attr, + const QString& context, + const Q3MimeSourceFactory& factory, + bool emptyTag, Q3TextDocument *doc); +#endif + +#ifndef QT_NO_TEXTCUSTOMITEM + void registerCustomItem(Q3TextCustomItem *i, Q3TextParagraph *p); + void unregisterCustomItem(Q3TextCustomItem *i, Q3TextParagraph *p); +#endif + + void setFlow(Q3TextFlow *f); + void takeFlow(); + Q3TextFlow *flow() const { return flow_; } + bool isPageBreakEnabled() const { return pages; } + void setPageBreakEnabled(bool b) { pages = b; } + + void setUseFormatCollection(bool b) { useFC = b; } + bool useFormatCollection() const { return useFC; } + +#ifndef QT_NO_TEXTCUSTOMITEM + Q3TextTableCell *tableCell() const { return tc; } + void setTableCell(Q3TextTableCell *c) { tc = c; } +#endif + + void setPlainText(const QString &text); + void setRichText(const QString &text, const QString &context, const Q3TextFormat *initialFormat = 0); + QString richText() const; + QString plainText() const; + + bool focusNextPrevChild(bool next); + + int alignment() const; + void setAlignment(int a); + + int *tabArray() const; + int tabStopWidth() const; + void setTabArray(int *a); + void setTabStops(int tw); + + void setUndoDepth(int depth) { commandHistory->setUndoDepth(depth); } + int undoDepth() const { return commandHistory->undoDepth(); } + + int length() const; + void clear(bool createEmptyParag = false); + + virtual Q3TextParagraph *createParagraph(Q3TextDocument *, Q3TextParagraph *pr = 0, Q3TextParagraph *nx = 0, bool updateIds = true); + void insertChild(Q3TextDocument *dc) { childList.append(dc); } + void removeChild(Q3TextDocument *dc) { childList.removeAll(dc); } + QList<Q3TextDocument *> children() const { return childList; } + + bool hasFocusParagraph() const; + QString focusHref() const; + QString focusName() const; + + void invalidateOriginalText() { oTextValid = false; oText = QLatin1String(""); } + +Q_SIGNALS: + void minimumWidthChanged(int); + +private: + Q_DISABLE_COPY(Q3TextDocument) + + void init(); + QPixmap *bufferPixmap(const QSize &s); + // HTML parser + bool hasPrefix(const QChar* doc, int length, int pos, QChar c); + bool hasPrefix(const QChar* doc, int length, int pos, const QString& s); +#ifndef QT_NO_TEXTCUSTOMITEM + Q3TextCustomItem* parseTable(const QMap<QString, QString> &attr, const Q3TextFormat &fmt, + const QChar* doc, int length, int& pos, Q3TextParagraph *curpar); +#endif + bool eatSpace(const QChar* doc, int length, int& pos, bool includeNbsp = false); + bool eat(const QChar* doc, int length, int& pos, QChar c); + QString parseOpenTag(const QChar* doc, int length, int& pos, QMap<QString, QString> &attr, bool& emptyTag); + QString parseCloseTag(const QChar* doc, int length, int& pos); + QChar parseHTMLSpecialChar(const QChar* doc, int length, int& pos); + QString parseWord(const QChar* doc, int length, int& pos, bool lower = true); + QChar parseChar(const QChar* doc, int length, int& pos, Q3StyleSheetItem::WhiteSpaceMode wsm); + void setRichTextInternal(const QString &text, Q3TextCursor* cursor = 0, const Q3TextFormat *initialFormat = 0); + void setRichTextMarginsInternal(QList< QVector<Q3StyleSheetItem *> *>& styles, Q3TextParagraph* stylesPar); + + struct Q_COMPAT_EXPORT Focus { + Q3TextParagraph *parag; + int start, len; + QString href; + QString name; + }; + + int cx, cy, cw, vw; + Q3TextParagraph *fParag, *lParag; + Q3TextPreProcessor *pProcessor; + struct SelectionColor { + QColor background; + QColor text; + }; + QMap<int, SelectionColor> selectionColors; + QMap<int, Q3TextDocumentSelection> selections; + Q3TextCommandHistory *commandHistory; + Q3TextFormatter *pFormatter; + Q3TextIndent *indenter; + Q3TextFormatCollection *fCollection; + Qt::TextFormat txtFormat; + uint preferRichText : 1; + uint pages : 1; + uint useFC : 1; + uint withoutDoubleBuffer : 1; + uint underlLinks : 1; + uint nextDoubleBuffered : 1; + uint oTextValid : 1; + uint mightHaveCustomItems : 1; + int align; + int nSelections; + Q3TextFlow *flow_; + Q3TextDocument *par; + Q3TextParagraph *parentPar; +#ifndef QT_NO_TEXTCUSTOMITEM + Q3TextTableCell *tc; +#endif + QBrush *backBrush; + QPixmap *buf_pixmap; + Focus focusIndicator; + int minw; + int wused; + int leftmargin; + int rightmargin; + Q3TextParagraph *minwParag, *curParag; + Q3StyleSheet* sheet_; +#ifndef QT_NO_MIME + Q3MimeSourceFactory* factory_; +#endif + QString contxt; + QMap<QString, QString> attribs; + int *tArray; + int tStopWidth; + int uDepth; + QString oText; + QList<Q3TextDocument *> childList; + QColor linkColor, bodyText; + double scaleFontsFactor; + + short list_tm,list_bm, list_lm, li_tm, li_bm, par_tm, par_bm; +}; + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +class Q_COMPAT_EXPORT Q3TextDeleteCommand : public Q3TextCommand +{ +public: + Q3TextDeleteCommand(Q3TextDocument *dc, int i, int idx, const QVector<Q3TextStringChar> &str, + const QByteArray& oldStyle); + Q3TextDeleteCommand(Q3TextParagraph *p, int idx, const QVector<Q3TextStringChar> &str); + virtual ~Q3TextDeleteCommand(); + + Commands type() const { return Delete; } + Q3TextCursor *execute(Q3TextCursor *c); + Q3TextCursor *unexecute(Q3TextCursor *c); + +protected: + int id, index; + Q3TextParagraph *parag; + QVector<Q3TextStringChar> text; + QByteArray styleInformation; + +}; + +class Q_COMPAT_EXPORT Q3TextInsertCommand : public Q3TextDeleteCommand +{ +public: + Q3TextInsertCommand(Q3TextDocument *dc, int i, int idx, const QVector<Q3TextStringChar> &str, + const QByteArray& oldStyleInfo) + : Q3TextDeleteCommand(dc, i, idx, str, oldStyleInfo) {} + Q3TextInsertCommand(Q3TextParagraph *p, int idx, const QVector<Q3TextStringChar> &str) + : Q3TextDeleteCommand(p, idx, str) {} + virtual ~Q3TextInsertCommand() {} + + Commands type() const { return Insert; } + Q3TextCursor *execute(Q3TextCursor *c) { return Q3TextDeleteCommand::unexecute(c); } + Q3TextCursor *unexecute(Q3TextCursor *c) { return Q3TextDeleteCommand::execute(c); } + +}; + +class Q_COMPAT_EXPORT Q3TextFormatCommand : public Q3TextCommand +{ +public: + Q3TextFormatCommand(Q3TextDocument *dc, int sid, int sidx, int eid, int eidx, const QVector<Q3TextStringChar> &old, Q3TextFormat *f, int fl); + virtual ~Q3TextFormatCommand(); + + Commands type() const { return Format; } + Q3TextCursor *execute(Q3TextCursor *c); + Q3TextCursor *unexecute(Q3TextCursor *c); + +protected: + int startId, startIndex, endId, endIndex; + Q3TextFormat *format; + QVector<Q3TextStringChar> oldFormats; + int flags; + +}; + +class Q_COMPAT_EXPORT Q3TextStyleCommand : public Q3TextCommand +{ +public: + Q3TextStyleCommand(Q3TextDocument *dc, int fParag, int lParag, const QByteArray& beforeChange ); + virtual ~Q3TextStyleCommand() {} + + Commands type() const { return Style; } + Q3TextCursor *execute(Q3TextCursor *c); + Q3TextCursor *unexecute(Q3TextCursor *c); + + static QByteArray readStyleInformation( Q3TextDocument* dc, int fParag, int lParag); + static void writeStyleInformation( Q3TextDocument* dc, int fParag, const QByteArray& style); + +private: + int firstParag, lastParag; + QByteArray before; + QByteArray after; +}; + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +struct Q_COMPAT_EXPORT Q3TextParagraphSelection +{ + int start, end; + Q_DUMMY_COMPARISON_OPERATOR(Q3TextParagraphSelection) +}; + +struct Q_COMPAT_EXPORT QTextLineStart +{ + QTextLineStart() : y(0), baseLine(0), h(0) + { } + QTextLineStart(int y_, int bl, int h_) : y(y_), baseLine(bl), h(h_), + w(0) + { } + +public: + int y, baseLine, h; + int w; +}; + +class Q_COMPAT_EXPORT Q3TextParagraphData +{ +public: + Q3TextParagraphData() {} + virtual ~Q3TextParagraphData(); + virtual void join(Q3TextParagraphData *); +}; + +class Q3TextParagraphPseudoDocument; + +class Q3SyntaxHighlighter; + +class Q_COMPAT_EXPORT Q3TextParagraph +{ + friend class Q3TextDocument; + friend class Q3TextCursor; + friend class Q3SyntaxHighlighter; + +public: + Q3TextParagraph(Q3TextDocument *dc, Q3TextParagraph *pr = 0, Q3TextParagraph *nx = 0, bool updateIds = true); + ~Q3TextParagraph(); + + Q3TextString *string() const; + Q3TextStringChar *at(int i) const; // maybe remove later + int leftGap() const; + int length() const; // maybe remove later + + void setListStyle(Q3StyleSheetItem::ListStyle ls) { lstyle = ls; changed = true; } + Q3StyleSheetItem::ListStyle listStyle() const { return (Q3StyleSheetItem::ListStyle)lstyle; } + void setListItem(bool li); + bool isListItem() const { return litem; } + void setListValue(int v) { list_val = v; } + int listValue() const { return list_val > 0 ? list_val : -1; } + + void setListDepth(int depth); + int listDepth() const { return ldepth; } + +// void setFormat(Q3TextFormat *fm); +// Q3TextFormat *paragFormat() const; + + inline Q3TextDocument *document() const { + if (hasdoc) return (Q3TextDocument*) docOrPseudo; + return 0; + } + Q3TextParagraphPseudoDocument *pseudoDocument() const; + + QRect rect() const; + void setHeight(int h) { r.setHeight(h); } + void show(); + void hide(); + bool isVisible() const { return visible; } + + Q3TextParagraph *prev() const; + Q3TextParagraph *next() const; + void setPrev(Q3TextParagraph *s); + void setNext(Q3TextParagraph *s); + + void insert(int index, const QString &s); + void insert(int index, const QChar *unicode, int len); + void append(const QString &s, bool reallyAtEnd = false); + void truncate(int index); + void remove(int index, int len); + void join(Q3TextParagraph *s); + + void invalidate(int chr); + + void move(int &dy); + void format(int start = -1, bool doMove = true); + + bool isValid() const; + bool hasChanged() const; + void setChanged(bool b, bool recursive = false); + + int lineHeightOfChar(int i, int *bl = 0, int *y = 0) const; + Q3TextStringChar *lineStartOfChar(int i, int *index = 0, int *line = 0) const; + int lines() const; + Q3TextStringChar *lineStartOfLine(int line, int *index = 0) const; + int lineY(int l) const; + int lineBaseLine(int l) const; + int lineHeight(int l) const; + void lineInfo(int l, int &y, int &h, int &bl) const; + + void setSelection(int id, int start, int end); + void removeSelection(int id); + int selectionStart(int id) const; + int selectionEnd(int id) const; + bool hasSelection(int id) const; + bool hasAnySelection() const; + bool fullSelected(int id) const; + + void setEndState(int s); + int endState() const; + + void setParagId(int i); + int paragId() const; + + bool firstPreProcess() const; + void setFirstPreProcess(bool b); + + void indent(int *oldIndent = 0, int *newIndent = 0); + + void setExtraData(Q3TextParagraphData *data); + Q3TextParagraphData *extraData() const; + + QMap<int, QTextLineStart*> &lineStartList(); + + void setFormat(int index, int len, Q3TextFormat *f, bool useCollection = true, int flags = -1); + + void setAlignment(int a); + int alignment() const; + + void paint(QPainter &painter, const QPalette &pal, Q3TextCursor *cursor = 0, + bool drawSelections = false, int clipx = -1, int clipy = -1, + int clipw = -1, int cliph = -1); + + int topMargin() const; + int bottomMargin() const; + int leftMargin() const; + int firstLineMargin() const; + int rightMargin() const; + int lineSpacing() const; + +#ifndef QT_NO_TEXTCUSTOMITEM + void registerFloatingItem(Q3TextCustomItem *i); + void unregisterFloatingItem(Q3TextCustomItem *i); +#endif + + void setFullWidth(bool b) { fullWidth = b; } + bool isFullWidth() const { return fullWidth; } + +#ifndef QT_NO_TEXTCUSTOMITEM + Q3TextTableCell *tableCell() const; +#endif + + QBrush *background() const; + + int documentWidth() const; + int documentVisibleWidth() const; + int documentX() const; + int documentY() const; + Q3TextFormatCollection *formatCollection() const; + Q3TextFormatter *formatter() const; + + int nextTab(int i, int x); + int *tabArray() const; + void setTabArray(int *a); + void setTabStops(int tw); + + void adjustToPainter(QPainter *p); + + void setNewLinesAllowed(bool b); + bool isNewLinesAllowed() const; + + QString richText() const; + + void addCommand(Q3TextCommand *cmd); + Q3TextCursor *undo(Q3TextCursor *c = 0); + Q3TextCursor *redo(Q3TextCursor *c = 0); + Q3TextCommandHistory *commands() const; + void copyParagData(Q3TextParagraph *parag); + + void setBreakable(bool b) { breakable = b; } + bool isBreakable() const { return breakable; } + + void setBackgroundColor(const QColor &c); + QColor *backgroundColor() const { return bgcol; } + void clearBackgroundColor(); + + void setMovedDown(bool b) { movedDown = b; } + bool wasMovedDown() const { return movedDown; } + + void setDirection(QChar::Direction); + QChar::Direction direction() const; + void setPaintDevice(QPaintDevice *pd) { paintdevice = pd; } + + void readStyleInformation(QDataStream& stream); + void writeStyleInformation(QDataStream& stream) const; + +protected: + void setColorForSelection(QColor &c, QPainter &p, const QPalette &pal, int selection); + void drawLabel(QPainter* p, int x, int y, int w, int h, int base, const QPalette &pal); + void drawString(QPainter &painter, const QString &str, int start, int len, int xstart, + int y, int baseLine, int w, int h, bool drawSelections, int fullSelectionWidth, + Q3TextStringChar *formatChar, const QPalette &pal, + bool rightToLeft); + +private: + QMap<int, Q3TextParagraphSelection> &selections() const; +#ifndef QT_NO_TEXTCUSTOMITEM + QList<Q3TextCustomItem *> &floatingItems() const; +#endif + inline QBrush backgroundBrush(const QPalette &pal) { + if (bgcol) + return *bgcol; + return pal.brush(QPalette::Base); + } + void invalidateStyleCache(); + + QMap<int, QTextLineStart*> lineStarts; + QRect r; + Q3TextParagraph *p, *n; + void *docOrPseudo; + uint changed : 1; + uint firstFormat : 1; + uint firstPProcess : 1; + uint needPreProcess : 1; + uint fullWidth : 1; + uint lastInFrame : 1; + uint visible : 1; + uint breakable : 1; + uint movedDown : 1; + uint mightHaveCustomItems : 1; + uint hasdoc : 1; + uint litem : 1; // whether the paragraph is a list item + uint rtext : 1; // whether the paragraph needs rich text margin + signed int align : 5; + uint /*Q3StyleSheetItem::ListStyle*/ lstyle : 4; + int invalid; + int state, id; + Q3TextString *str; + QMap<int, Q3TextParagraphSelection> *mSelections; +#ifndef QT_NO_TEXTCUSTOMITEM + QList<Q3TextCustomItem *> *mFloatingItems; +#endif + short utm, ubm, ulm, urm, uflm, ulinespacing; + short tabStopWidth, minwidth; + int *tArray; + Q3TextParagraphData *eData; + short list_val; + ushort ldepth; + QColor *bgcol; + QPaintDevice *paintdevice; +}; + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_COMPAT_EXPORT Q3TextFormatter +{ +public: + Q3TextFormatter(); + virtual ~Q3TextFormatter(); + + virtual int format(Q3TextDocument *doc, Q3TextParagraph *parag, int start, const QMap<int, QTextLineStart*> &oldLineStarts) = 0; + virtual int formatVertically(Q3TextDocument* doc, Q3TextParagraph* parag); + + bool isWrapEnabled(Q3TextParagraph *p) const { if (!wrapEnabled) return false; if (p && !p->isBreakable()) return false; return true;} + int wrapAtColumn() const { return wrapColumn;} + virtual void setWrapEnabled(bool b); + virtual void setWrapAtColumn(int c); + virtual void setAllowBreakInWords(bool b) { biw = b; } + bool allowBreakInWords() const { return biw; } + + int minimumWidth() const { return thisminw; } + int widthUsed() const { return thiswused; } + +protected: + virtual QTextLineStart *formatLine(Q3TextParagraph *parag, Q3TextString *string, QTextLineStart *line, Q3TextStringChar *start, + Q3TextStringChar *last, int align = Qt::AlignAuto, int space = 0); +#ifndef QT_NO_COMPLEXTEXT + virtual QTextLineStart *bidiReorderLine(Q3TextParagraph *parag, Q3TextString *string, QTextLineStart *line, Q3TextStringChar *start, + Q3TextStringChar *last, int align, int space); +#endif + void insertLineStart(Q3TextParagraph *parag, int index, QTextLineStart *ls); + + int thisminw; + int thiswused; + +private: + bool wrapEnabled; + int wrapColumn; + bool biw; +}; + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_COMPAT_EXPORT Q3TextFormatterBreakInWords : public Q3TextFormatter +{ +public: + Q3TextFormatterBreakInWords(); + virtual ~Q3TextFormatterBreakInWords() {} + + int format(Q3TextDocument *doc, Q3TextParagraph *parag, int start, const QMap<int, QTextLineStart*> &oldLineStarts); + +}; + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_COMPAT_EXPORT Q3TextFormatterBreakWords : public Q3TextFormatter +{ +public: + Q3TextFormatterBreakWords(); + virtual ~Q3TextFormatterBreakWords() {} + + int format(Q3TextDocument *doc, Q3TextParagraph *parag, int start, const QMap<int, QTextLineStart*> &oldLineStarts); + +}; + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_COMPAT_EXPORT Q3TextIndent +{ +public: + Q3TextIndent(); + virtual ~Q3TextIndent() {} + + virtual void indent(Q3TextDocument *doc, Q3TextParagraph *parag, int *oldIndent = 0, int *newIndent = 0) = 0; + +}; + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_COMPAT_EXPORT Q3TextPreProcessor +{ +public: + enum Ids { + Standard = 0 + }; + + Q3TextPreProcessor(); + virtual ~Q3TextPreProcessor() {} + + virtual void process(Q3TextDocument *doc, Q3TextParagraph *, int, bool = true) = 0; + virtual Q3TextFormat *format(int id) = 0; + +}; + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_COMPAT_EXPORT Q3TextFormat +{ + friend class Q3TextFormatCollection; + friend class Q3TextDocument; + +public: + enum Flags { + NoFlags, + Bold = 1, + Italic = 2, + Underline = 4, + Family = 8, + Size = 16, + Color = 32, + Misspelled = 64, + VAlign = 128, + StrikeOut= 256, + Font = Bold | Italic | Underline | Family | Size | StrikeOut, + Format = Font | Color | Misspelled | VAlign + }; + + enum VerticalAlignment { AlignNormal, AlignSuperScript, AlignSubScript }; + + Q3TextFormat(); + virtual ~Q3TextFormat(); + + Q3TextFormat(const Q3StyleSheetItem *s); + Q3TextFormat(const QFont &f, const QColor &c, Q3TextFormatCollection *parent = 0); + Q3TextFormat(const Q3TextFormat &fm); + Q3TextFormat makeTextFormat(const Q3StyleSheetItem *style, const QMap<QString,QString>& attr, double scaleFontsFactor) const; + Q3TextFormat& operator=(const Q3TextFormat &fm); + QColor color() const; + QFont font() const; + QFontMetrics fontMetrics() const { return fm; } + bool isMisspelled() const; + VerticalAlignment vAlign() const; + int minLeftBearing() const; + int minRightBearing() const; + int width(const QChar &c) const; + int width(const QString &str, int pos) const; + int height() const; + int ascent() const; + int descent() const; + int leading() const; + bool useLinkColor() const; + + void setBold(bool b); + void setItalic(bool b); + void setUnderline(bool b); + void setStrikeOut(bool b); + void setFamily(const QString &f); + void setPointSize(int s); + void setFont(const QFont &f); + void setColor(const QColor &c); + void setMisspelled(bool b); + void setVAlign(VerticalAlignment a); + + bool operator==(const Q3TextFormat &f) const; + Q3TextFormatCollection *parent() const; + const QString &key() const; + + static QString getKey(const QFont &f, const QColor &c, bool misspelled, VerticalAlignment vAlign); + + void addRef(); + void removeRef(); + + QString makeFormatChangeTags(Q3TextFormat* defaultFormat, Q3TextFormat *f, const QString& oldAnchorHref, const QString& anchorHref) const; + QString makeFormatEndTags(Q3TextFormat* defaultFormat, const QString& anchorHref) const; + + static void setPainter(QPainter *p); + static QPainter* painter(); + + bool fontSizesInPixels() { return usePixelSizes; } + +protected: + virtual void generateKey(); + +private: + void update(); + static void applyFont(const QFont &f); + +private: + QFont fn; + QColor col; + QFontMetrics fm; + uint missp : 1; + uint linkColor : 1; + uint usePixelSizes : 1; + int leftBearing, rightBearing; + VerticalAlignment ha; + uchar widths[256]; + int hei, asc, dsc; + Q3TextFormatCollection *collection; + int ref; + QString k; + int logicalFontSize; + int stdSize; + static QPainter *pntr; + static QFontMetrics *pntr_fm; + static int pntr_asc; + static int pntr_hei; + static int pntr_ldg; + static int pntr_dsc; + +}; + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_COMPAT_EXPORT Q3TextFormatCollection +{ + friend class Q3TextDocument; + friend class Q3TextFormat; + +public: + Q3TextFormatCollection(); + virtual ~Q3TextFormatCollection(); + + void setDefaultFormat(Q3TextFormat *f); + Q3TextFormat *defaultFormat() const; + virtual Q3TextFormat *format(Q3TextFormat *f); + virtual Q3TextFormat *format(Q3TextFormat *of, Q3TextFormat *nf, int flags); + virtual Q3TextFormat *format(const QFont &f, const QColor &c); + virtual void remove(Q3TextFormat *f); + virtual Q3TextFormat *createFormat(const Q3TextFormat &f) { return new Q3TextFormat(f); } + virtual Q3TextFormat *createFormat(const QFont &f, const QColor &c) { return new Q3TextFormat(f, c, this); } + + void updateDefaultFormat(const QFont &font, const QColor &c, Q3StyleSheet *sheet); + + QPaintDevice *paintDevice() const { return paintdevice; } + void setPaintDevice(QPaintDevice *); + +private: + void updateKeys(); + +private: + Q3TextFormat *defFormat, *lastFormat, *cachedFormat; + QHash<QString, Q3TextFormat *> cKey; + Q3TextFormat *cres; + QFont cfont; + QColor ccol; + QString kof, knf; + int cflags; + + QPaintDevice *paintdevice; +}; + +class Q_COMPAT_EXPORT Q3TextParagraphPseudoDocument +{ +public: + Q3TextParagraphPseudoDocument(); + ~Q3TextParagraphPseudoDocument(); + QRect docRect; + Q3TextFormatter *pFormatter; + Q3TextCommandHistory *commandHistory; + int minw; + int wused; + Q3TextFormatCollection collection; +}; + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +inline int Q3TextParagraph::length() const +{ + return str->length(); +} + +inline QRect Q3TextParagraph::rect() const +{ + return r; +} + +inline int Q3TextCursor::index() const +{ + return idx; +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +inline int Q3TextDocument::x() const +{ + return cx; +} + +inline int Q3TextDocument::y() const +{ + return cy; +} + +inline int Q3TextDocument::width() const +{ + return qMax(cw, flow_->width()); +} + +inline int Q3TextDocument::visibleWidth() const +{ + return vw; +} + +inline Q3TextParagraph *Q3TextDocument::firstParagraph() const +{ + return fParag; +} + +inline Q3TextParagraph *Q3TextDocument::lastParagraph() const +{ + return lParag; +} + +inline void Q3TextDocument::setFirstParagraph(Q3TextParagraph *p) +{ + fParag = p; +} + +inline void Q3TextDocument::setLastParagraph(Q3TextParagraph *p) +{ + lParag = p; +} + +inline void Q3TextDocument::setWidth(int w) +{ + cw = qMax(w, minw); + flow_->setWidth(cw); + vw = w; +} + +inline int Q3TextDocument::minimumWidth() const +{ + return minw; +} + +inline void Q3TextDocument::setY(int y) +{ + cy = y; +} + +inline int Q3TextDocument::leftMargin() const +{ + return leftmargin; +} + +inline void Q3TextDocument::setLeftMargin(int lm) +{ + leftmargin = lm; +} + +inline int Q3TextDocument::rightMargin() const +{ + return rightmargin; +} + +inline void Q3TextDocument::setRightMargin(int rm) +{ + rightmargin = rm; +} + +inline Q3TextPreProcessor *Q3TextDocument::preProcessor() const +{ + return pProcessor; +} + +inline void Q3TextDocument::setPreProcessor(Q3TextPreProcessor * sh) +{ + pProcessor = sh; +} + +inline void Q3TextDocument::setFormatter(Q3TextFormatter *f) +{ + delete pFormatter; + pFormatter = f; +} + +inline Q3TextFormatter *Q3TextDocument::formatter() const +{ + return pFormatter; +} + +inline void Q3TextDocument::setIndent(Q3TextIndent *i) +{ + indenter = i; +} + +inline Q3TextIndent *Q3TextDocument::indent() const +{ + return indenter; +} + +inline QColor Q3TextDocument::selectionColor(int id) const +{ + const Q3TextDocument *p = this; + while (p->par) + p = p->par; + return p->selectionColors[id].background; +} + +inline QColor Q3TextDocument::selectionTextColor(int id) const +{ + const Q3TextDocument *p = this; + while (p->par) + p = p->par; + return p->selectionColors[id].text; +} + +inline bool Q3TextDocument::hasSelectionTextColor(int id) const +{ + const Q3TextDocument *p = this; + while (p->par) + p = p->par; + return p->selectionColors.contains(id); +} + +inline void Q3TextDocument::setSelectionColor(int id, const QColor &c) +{ + Q3TextDocument *p = this; + while (p->par) + p = p->par; + p->selectionColors[id].background = c; +} + +inline void Q3TextDocument::setSelectionTextColor(int id, const QColor &c) +{ + Q3TextDocument *p = this; + while (p->par) + p = p->par; + p->selectionColors[id].text = c; +} + +inline Q3TextFormatCollection *Q3TextDocument::formatCollection() const +{ + return fCollection; +} + +inline int Q3TextDocument::alignment() const +{ + return align; +} + +inline void Q3TextDocument::setAlignment(int a) +{ + align = a; +} + +inline int *Q3TextDocument::tabArray() const +{ + return tArray; +} + +inline int Q3TextDocument::tabStopWidth() const +{ + return tStopWidth; +} + +inline void Q3TextDocument::setTabArray(int *a) +{ + tArray = a; +} + +inline void Q3TextDocument::setTabStops(int tw) +{ + tStopWidth = tw; +} + +inline QString Q3TextDocument::originalText() const +{ + if (oTextValid) + return oText; + return text(); +} + +inline void Q3TextDocument::setFlow(Q3TextFlow *f) +{ + if (flow_) + delete flow_; + flow_ = f; +} + +inline void Q3TextDocument::takeFlow() +{ + flow_ = 0; +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +inline QColor Q3TextFormat::color() const +{ + return col; +} + +inline QFont Q3TextFormat::font() const +{ + return fn; +} + +inline bool Q3TextFormat::isMisspelled() const +{ + return missp; +} + +inline Q3TextFormat::VerticalAlignment Q3TextFormat::vAlign() const +{ + return ha; +} + +inline bool Q3TextFormat::operator==(const Q3TextFormat &f) const +{ + return k == f.k; +} + +inline Q3TextFormatCollection *Q3TextFormat::parent() const +{ + return collection; +} + +inline void Q3TextFormat::addRef() +{ + ref++; +} + +inline void Q3TextFormat::removeRef() +{ + ref--; + if (!collection) + return; + if (this == collection->defFormat) + return; + if (ref == 0) + collection->remove(this); +} + +inline const QString &Q3TextFormat::key() const +{ + return k; +} + +inline bool Q3TextFormat::useLinkColor() const +{ + return linkColor; +} + +inline Q3TextStringChar *Q3TextParagraph::at(int i) const +{ + return &str->at(i); +} + +inline bool Q3TextParagraph::isValid() const +{ + return invalid == -1; +} + +inline bool Q3TextParagraph::hasChanged() const +{ + return changed; +} + +inline void Q3TextParagraph::setBackgroundColor(const QColor & c) +{ + delete bgcol; + bgcol = new QColor(c); + setChanged(true); +} + +inline void Q3TextParagraph::clearBackgroundColor() +{ + delete bgcol; bgcol = 0; setChanged(true); +} + +inline void Q3TextParagraph::append(const QString &s, bool reallyAtEnd) +{ + if (reallyAtEnd) { + insert(str->length(), s); + } else { + int str_end = str->length() - 1; + insert(str_end > 0 ? str_end : 0, s); + } +} + +inline Q3TextParagraph *Q3TextParagraph::prev() const +{ + return p; +} + +inline Q3TextParagraph *Q3TextParagraph::next() const +{ + return n; +} + +inline bool Q3TextParagraph::hasAnySelection() const +{ + return mSelections ? !selections().isEmpty() : false; +} + +inline void Q3TextParagraph::setEndState(int s) +{ + if (s == state) + return; + state = s; +} + +inline int Q3TextParagraph::endState() const +{ + return state; +} + +inline void Q3TextParagraph::setParagId(int i) +{ + id = i; +} + +inline int Q3TextParagraph::paragId() const +{ + if (id == -1) + qWarning("invalid parag id!!!!!!!! (%p)", (void*)this); + return id; +} + +inline bool Q3TextParagraph::firstPreProcess() const +{ + return firstPProcess; +} + +inline void Q3TextParagraph::setFirstPreProcess(bool b) +{ + firstPProcess = b; +} + +inline QMap<int, QTextLineStart*> &Q3TextParagraph::lineStartList() +{ + return lineStarts; +} + +inline Q3TextString *Q3TextParagraph::string() const +{ + return str; +} + +inline Q3TextParagraphPseudoDocument *Q3TextParagraph::pseudoDocument() const +{ + if (hasdoc) + return 0; + return (Q3TextParagraphPseudoDocument*) docOrPseudo; +} + + +#ifndef QT_NO_TEXTCUSTOMITEM +inline Q3TextTableCell *Q3TextParagraph::tableCell() const +{ + return hasdoc ? document()->tableCell () : 0; +} +#endif + +inline Q3TextCommandHistory *Q3TextParagraph::commands() const +{ + return hasdoc ? document()->commands() : pseudoDocument()->commandHistory; +} + + +inline int Q3TextParagraph::alignment() const +{ + return align; +} + +#ifndef QT_NO_TEXTCUSTOMITEM +inline void Q3TextParagraph::registerFloatingItem(Q3TextCustomItem *i) +{ + floatingItems().append(i); +} + +inline void Q3TextParagraph::unregisterFloatingItem(Q3TextCustomItem *i) +{ + floatingItems().removeAll(i); +} +#endif + +inline QBrush *Q3TextParagraph::background() const +{ +#ifndef QT_NO_TEXTCUSTOMITEM + return tableCell() ? tableCell()->backGround() : 0; +#else + return 0; +#endif +} + +inline int Q3TextParagraph::documentWidth() const +{ + return hasdoc ? document()->width() : pseudoDocument()->docRect.width(); +} + +inline int Q3TextParagraph::documentVisibleWidth() const +{ + return hasdoc ? document()->visibleWidth() : pseudoDocument()->docRect.width(); +} + +inline int Q3TextParagraph::documentX() const +{ + return hasdoc ? document()->x() : pseudoDocument()->docRect.x(); +} + +inline int Q3TextParagraph::documentY() const +{ + return hasdoc ? document()->y() : pseudoDocument()->docRect.y(); +} + +inline void Q3TextParagraph::setExtraData(Q3TextParagraphData *data) +{ + eData = data; +} + +inline Q3TextParagraphData *Q3TextParagraph::extraData() const +{ + return eData; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +inline void Q3TextFormatCollection::setDefaultFormat(Q3TextFormat *f) +{ + defFormat = f; +} + +inline Q3TextFormat *Q3TextFormatCollection::defaultFormat() const +{ + return defFormat; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +inline Q3TextFormat *Q3TextStringChar::format() const +{ + return (type == Regular) ? p.format : p.custom->format; +} + + +#ifndef QT_NO_TEXTCUSTOMITEM +inline Q3TextCustomItem *Q3TextStringChar::customItem() const +{ + return isCustom() ? p.custom->custom : 0; +} +#endif + +inline int Q3TextStringChar::height() const +{ +#ifndef QT_NO_TEXTCUSTOMITEM + return !isCustom() ? format()->height() : (customItem()->placement() == Q3TextCustomItem::PlaceInline ? customItem()->height : 0); +#else + return format()->height(); +#endif +} + +inline int Q3TextStringChar::ascent() const +{ +#ifndef QT_NO_TEXTCUSTOMITEM + return !isCustom() ? format()->ascent() : (customItem()->placement() == Q3TextCustomItem::PlaceInline ? customItem()->ascent() : 0); +#else + return format()->ascent(); +#endif +} + +inline int Q3TextStringChar::descent() const +{ +#ifndef QT_NO_TEXTCUSTOMITEM + return !isCustom() ? format()->descent() : 0; +#else + return format()->descent(); +#endif +} + +#endif // QT_NO_RICHTEXT + +QT_END_NAMESPACE + +#endif // Q3RICHTEXT_P_H diff --git a/src/qt3support/text/q3simplerichtext.cpp b/src/qt3support/text/q3simplerichtext.cpp new file mode 100644 index 0000000..5abf04a --- /dev/null +++ b/src/qt3support/text/q3simplerichtext.cpp @@ -0,0 +1,421 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3simplerichtext.h" + +#ifndef QT_NO_RICHTEXT +#include "q3richtext_p.h" +#include "qapplication.h" + +QT_BEGIN_NAMESPACE + +class Q3SimpleRichTextData +{ +public: + Q3TextDocument *doc; + QFont font; + int cachedWidth; + bool cachedWidthWithPainter; + void adjustSize(); +}; + +// Pull this private function in from qglobal.cpp +Q_CORE_EXPORT unsigned int qt_int_sqrt(unsigned int n); + +void Q3SimpleRichTextData::adjustSize() { + QFontMetrics fm(font); + int mw = fm.width(QString(QLatin1Char('x'))) * 80; + int w = mw; + doc->doLayout(0,w); + if (doc->widthUsed() != 0) { + w = qt_int_sqrt(5 * doc->height() * doc->widthUsed() / 3); + doc->doLayout(0, qMin(w, mw)); + + if (w*3 < 5*doc->height()) { + w = qt_int_sqrt(2 * doc->height() * doc->widthUsed()); + doc->doLayout(0,qMin(w, mw)); + } + } + cachedWidth = doc->width(); + cachedWidthWithPainter = false; +} + +/*! + \class Q3SimpleRichText + \brief The Q3SimpleRichText class provides a small displayable piece of rich text. + + \compat + + This class encapsulates simple rich text usage in which a string + is interpreted as rich text and can be drawn. This is particularly + useful if you want to display some rich text in a custom widget. A + Q3StyleSheet is needed to interpret the tags and format the rich + text. Qt provides a default HTML-like style sheet, but you may + define custom style sheets. + + Once created, the rich text object can be queried for its width(), + height(), and the actual width used (see widthUsed()). Most + importantly, it can be drawn on any given QPainter with draw(). + Q3SimpleRichText can also be used to implement hypertext or active + text facilities by using anchorAt(). A hit test through inText() + makes it possible to use simple rich text for text objects in + editable drawing canvases. + + Once constructed from a string the contents cannot be changed, + only resized. If the contents change, just throw the rich text + object away and make a new one with the new contents. + + For large documents use QTextEdit or QTextBrowser. For very small + items of rich text you can use a QLabel. + + If you are using Q3SimpleRichText to print in high resolution you + should call setWidth(QPainter, int) so that the content will be + laid out properly on the page. +*/ + +/*! + Constructs a Q3SimpleRichText from the rich text string \a text and + the font \a fnt. + + The font is used as a basis for the text rendering. When using + rich text rendering on a widget \e w, you would normally specify + the widget's font, for example: + + \snippet doc/src/snippets/code/src_qt3support_text_q3simplerichtext.cpp 0 + + \a context is the optional context of the rich text object. This + becomes important if \a text contains relative references, for + example within image tags. Q3SimpleRichText always uses the default + mime source factory (see \l{Q3MimeSourceFactory::defaultFactory()}) + to resolve those references. The context will then be used to + calculate the absolute path. See + Q3MimeSourceFactory::makeAbsolute() for details. + + The \a sheet is an optional style sheet. If it is 0, the default + style sheet will be used (see \l{Q3StyleSheet::defaultSheet()}). +*/ + +Q3SimpleRichText::Q3SimpleRichText(const QString& text, const QFont& fnt, + const QString& context, const Q3StyleSheet* sheet) +{ + d = new Q3SimpleRichTextData; + d->cachedWidth = -1; + d->cachedWidthWithPainter = false; + d->font = fnt; + d->doc = new Q3TextDocument(0); + d->doc->setTextFormat(Qt::RichText); + d->doc->setLeftMargin(0); + d->doc->setRightMargin(0); + d->doc->setFormatter(new Q3TextFormatterBreakWords); + d->doc->setStyleSheet((Q3StyleSheet*)sheet); + d->doc->setDefaultFormat(fnt, QColor()); + d->doc->setText(text, context); +} + + +/*! + Constructs a Q3SimpleRichText from the rich text string \a text and + the font \a fnt. + + This is a slightly more complex constructor for Q3SimpleRichText + that takes an additional mime source factory \a factory, a page + break parameter \a pageBreak and a bool \a linkUnderline. \a + linkColor is only provided for compatibility, but has no effect, + as QPalette::link() color is used now. + + \a context is the optional context of the rich text object. This + becomes important if \a text contains relative references, for + example within image tags. Q3SimpleRichText always uses the default + mime source factory (see \l{Q3MimeSourceFactory::defaultFactory()}) + to resolve those references. The context will then be used to + calculate the absolute path. See + Q3MimeSourceFactory::makeAbsolute() for details. + + The \a sheet is an optional style sheet. If it is 0, the default + style sheet will be used (see \l{Q3StyleSheet::defaultSheet()}). + + This constructor is useful for creating a Q3SimpleRichText object + suitable for printing. Set \a pageBreak to be the height of the + contents area of the pages. +*/ + +Q3SimpleRichText::Q3SimpleRichText(const QString& text, const QFont& fnt, + const QString& context, const Q3StyleSheet* sheet, + const Q3MimeSourceFactory* factory, int pageBreak, + const QColor& /*linkColor*/, bool linkUnderline) +{ + d = new Q3SimpleRichTextData; + d->cachedWidth = -1; + d->cachedWidthWithPainter = false; + d->font = fnt; + d->doc = new Q3TextDocument(0); + d->doc->setTextFormat(Qt::RichText); + d->doc->setFormatter(new Q3TextFormatterBreakWords); + d->doc->setStyleSheet((Q3StyleSheet*)sheet); + d->doc->setDefaultFormat(fnt, QColor()); + d->doc->flow()->setPageSize(pageBreak); + d->doc->setPageBreakEnabled(true); +#ifndef QT_NO_MIME + d->doc->setMimeSourceFactory((Q3MimeSourceFactory*)factory); +#endif + d->doc->setUnderlineLinks(linkUnderline); + d->doc->setText(text, context); +} + +/*! + Destroys the rich text object, freeing memory. +*/ + +Q3SimpleRichText::~Q3SimpleRichText() +{ + delete d->doc; + delete d; +} + +/*! + \overload + + Sets the width of the rich text object to \a w pixels. + + \sa height(), adjustSize() +*/ + +void Q3SimpleRichText::setWidth(int w) +{ + if (w == d->cachedWidth && !d->cachedWidthWithPainter) + return; + d->doc->formatter()->setAllowBreakInWords(d->doc->isPageBreakEnabled()); + d->cachedWidth = w; + d->cachedWidthWithPainter = false; + d->doc->doLayout(0, w); +} + +/*! + Sets the width of the rich text object to \a w pixels, + recalculating the layout as if it were to be drawn with painter \a + p. + + Passing a painter is useful when you intend drawing on devices + other than the screen, for example a QPrinter. + + \sa height(), adjustSize() +*/ + +void Q3SimpleRichText::setWidth(QPainter *p, int w) +{ + if (w == d->cachedWidth && d->cachedWidthWithPainter) + return; + d->doc->formatter()->setAllowBreakInWords(d->doc->isPageBreakEnabled() || + (p && p->device() && + p->device()->devType() == QInternal::Printer)); + p->save(); + d->cachedWidth = w; + d->cachedWidthWithPainter = true; + d->doc->doLayout(p, w); + p->restore(); +} + +/*! + Returns the set width of the rich text object in pixels. + + \sa widthUsed() +*/ + +int Q3SimpleRichText::width() const +{ + if (d->cachedWidth < 0) + d->adjustSize(); + return d->doc->width(); +} + +/*! + Returns the width in pixels that is actually used by the rich text + object. This can be smaller or wider than the set width. + + It may be wider, for example, if the text contains images or + non-breakable words that are already wider than the available + space. It's smaller when the object only consists of lines that do + not fill the width completely. + + \sa width() +*/ + +int Q3SimpleRichText::widthUsed() const +{ + if (d->cachedWidth < 0) + d->adjustSize(); + return d->doc->widthUsed(); +} + +/*! + Returns the height of the rich text object in pixels. + + \sa setWidth() +*/ + +int Q3SimpleRichText::height() const +{ + if (d->cachedWidth < 0) + d->adjustSize(); + return d->doc->height(); +} + +/*! + Adjusts the rich text object to a reasonable size. + + \sa setWidth() +*/ + +void Q3SimpleRichText::adjustSize() +{ + d->adjustSize(); +} + +/*! + Draws the formatted text with painter \a p, at position (\a x, \a + y), clipped to \a clipRect. The clipping rectangle is given in the + rich text object's coordinates translated by (\a x, \a y). Passing + an null rectangle results in no clipping. Colors from the color + group \a cg are used as needed, and if not 0, *\a{paper} is + used as the background brush. + + Note that the display code is highly optimized to reduce flicker, + so passing a brush for \a paper is preferable to simply clearing + the area to be painted and then calling this without a brush. +*/ + +void Q3SimpleRichText::draw(QPainter *p, int x, int y, const QRect& clipRect, + const QColorGroup &cg, const QBrush* paper) const +{ + p->save(); + if (d->cachedWidth < 0) + d->adjustSize(); + QRect r = clipRect; + if (!r.isNull()) + r.moveBy(-x, -y); + + if (paper) + d->doc->setPaper(new QBrush(*paper)); + QPalette pal2 = cg; + if (d->doc->paper()) + pal2.setBrush(QPalette::Base, *d->doc->paper()); + + if (!clipRect.isNull()) + p->setClipRect(clipRect); + p->translate(x, y); + d->doc->draw(p, r, pal2, paper); + p->translate(-x, -y); + p->restore(); +} + + +/*! \fn void Q3SimpleRichText::draw(QPainter *p, int x, int y, const QRegion& clipRegion, + const QColorGroup &cg, const QBrush* paper) const + + Use the version with clipRect instead of this \a clipRegion version, + since this region version has problems with larger documents on some + platforms (on X11 regions internally are represented with 16-bit + coordinates). +*/ + + + +/*! + Returns the context of the rich text object. If no context has + been specified in the constructor, an empty string is returned. The + context is the path to use to look up relative links, such as + image tags and anchor references. +*/ + +QString Q3SimpleRichText::context() const +{ + return d->doc->context(); +} + +/*! + Returns the anchor at the requested position, \a pos. An empty + string is returned if no anchor is specified for this position. +*/ + +QString Q3SimpleRichText::anchorAt(const QPoint& pos) const +{ + if (d->cachedWidth < 0) + d->adjustSize(); + Q3TextCursor c(d->doc); + c.place(pos, d->doc->firstParagraph(), true); + return c.paragraph()->at(c.index())->anchorHref(); +} + +/*! + Returns true if \a pos is within a text line of the rich text + object; otherwise returns false. +*/ + +bool Q3SimpleRichText::inText(const QPoint& pos) const +{ + if (d->cachedWidth < 0) + d->adjustSize(); + if (pos.y() > d->doc->height()) + return false; + Q3TextCursor c(d->doc); + c.place(pos, d->doc->firstParagraph()); + return c.totalOffsetX() + c.paragraph()->at(c.index())->x + + c.paragraph()->at(c.index())->format()->width(c.paragraph()->at(c.index())->c) > pos.x(); +} + +/*! + Sets the default font for the rich text object to \a f +*/ + +void Q3SimpleRichText::setDefaultFont(const QFont &f) +{ + if (d->font == f) + return; + d->font = f; + d->cachedWidth = -1; + d->cachedWidthWithPainter = false; + d->doc->setDefaultFormat(f, QColor()); + d->doc->setText(d->doc->originalText(), d->doc->context()); +} + +QT_END_NAMESPACE + +#endif //QT_NO_RICHTEXT diff --git a/src/qt3support/text/q3simplerichtext.h b/src/qt3support/text/q3simplerichtext.h new file mode 100644 index 0000000..450ce62 --- /dev/null +++ b/src/qt3support/text/q3simplerichtext.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3SIMPLERICHTEXT_H +#define Q3SIMPLERICHTEXT_H + +#include <QtCore/qnamespace.h> +#include <QtCore/qstring.h> +#include <QtGui/qregion.h> +#include <QtGui/qcolor.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3SupportLight) + +#ifndef QT_NO_RICHTEXT + +class QPainter; +class QWidget; +class Q3StyleSheet; +class QBrush; +class Q3MimeSourceFactory; +class Q3SimpleRichTextData; + +class Q_COMPAT_EXPORT Q3SimpleRichText +{ +public: + Q3SimpleRichText(const QString& text, const QFont& fnt, + const QString& context = QString(), const Q3StyleSheet* sheet = 0); + Q3SimpleRichText(const QString& text, const QFont& fnt, + const QString& context, const Q3StyleSheet *sheet, + const Q3MimeSourceFactory* factory, int pageBreak = -1, + const QColor& linkColor = Qt::blue, bool linkUnderline = true); + ~Q3SimpleRichText(); + + void setWidth(int); + void setWidth(QPainter*, int); + void setDefaultFont(const QFont &f); + int width() const; + int widthUsed() const; + int height() const; + void adjustSize(); + + void draw(QPainter* p, int x, int y, const QRect& clipRect, + const QColorGroup& cg, const QBrush* paper = 0) const; + + void draw(QPainter* p, int x, int y, const QRegion& clipRegion, + const QColorGroup& cg, const QBrush* paper = 0) const { + draw(p, x, y, clipRegion.boundingRect(), cg, paper); + } + + QString context() const; + QString anchorAt(const QPoint& pos) const; + + bool inText(const QPoint& pos) const; + +private: + Q_DISABLE_COPY(Q3SimpleRichText) + + Q3SimpleRichTextData* d; +}; + +#endif // QT_NO_RICHTEXT + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3SIMPLERICHTEXT_H diff --git a/src/qt3support/text/q3stylesheet.cpp b/src/qt3support/text/q3stylesheet.cpp new file mode 100644 index 0000000..ec39f5d --- /dev/null +++ b/src/qt3support/text/q3stylesheet.cpp @@ -0,0 +1,1471 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3stylesheet.h" + +#ifndef QT_NO_RICHTEXT + +#include "qlayout.h" +#include "qpainter.h" +#include "q3cleanuphandler.h" +#include <qtextdocument.h> + +#include <stdio.h> + +QT_BEGIN_NAMESPACE + +class Q3StyleSheetItemData +{ +public: + Q3StyleSheetItem::DisplayMode disp; + int fontitalic; + int fontunderline; + int fontstrikeout; + int fontweight; + int fontsize; + int fontsizelog; + int fontsizestep; + int lineSpacing; + QString fontfamily; + Q3StyleSheetItem *parentstyle; + QString stylename; + int ncolumns; + QColor col; + bool anchor; + int align; + Q3StyleSheetItem::VerticalAlignment valign; + int margin[5]; + Q3StyleSheetItem::ListStyle list; + Q3StyleSheetItem::WhiteSpaceMode whitespacemode; + QString contxt; + bool selfnest; + Q3StyleSheet* sheet; +}; + +/*! + \class Q3StyleSheetItem + \brief The Q3StyleSheetItem class provides an encapsulation of a set of text styles. + + \compat + + A style sheet item consists of a name and a set of attributes that + specify its font, color, etc. When used in a \link Q3StyleSheet + style sheet\endlink (see styleSheet()), items define the name() of + a rich text tag and the display property changes associated with + it. + + The \link Q3StyleSheetItem::DisplayMode display mode\endlink + attribute indicates whether the item is a block, an inline element + or a list element; see setDisplayMode(). The treatment of + whitespace is controlled by the \link + Q3StyleSheetItem::WhiteSpaceMode white space mode\endlink; see + setWhiteSpaceMode(). An item's margins are set with setMargin(), + In the case of list items, the list style is set with + setListStyle(). An item may be a hypertext link anchor; see + setAnchor(). Other attributes are set with setAlignment(), + setVerticalAlignment(), setFontFamily(), setFontSize(), + setFontWeight(), setFontItalic(), setFontUnderline(), + setFontStrikeOut and setColor(). +*/ + +/*! \enum Q3StyleSheetItem::AdditionalStyleValues + \internal +*/ + +/*! + \enum Q3StyleSheetItem::WhiteSpaceMode + + This enum defines the ways in which Q3StyleSheet can treat + whitespace. + + \value WhiteSpaceNormal any sequence of whitespace (including + line-breaks) is equivalent to a single space. + + \value WhiteSpacePre whitespace must be output exactly as given + in the input. + + \value WhiteSpaceNoWrap multiple spaces are collapsed as with + WhiteSpaceNormal, but no automatic line-breaks occur. To break + lines manually, use the \c{<br>} tag. + + \omitvalue WhiteSpaceModeUndefined +*/ + +/*! + \enum Q3StyleSheetItem::Margin + + \value MarginLeft left margin + \value MarginRight right margin + \value MarginTop top margin + \value MarginBottom bottom margin + \value MarginAll all margins (left, right, top and bottom) + \value MarginVertical top and bottom margins + \value MarginHorizontal left and right margins + \value MarginFirstLine margin (indentation) of the first line of + a paragarph (in addition to the MarginLeft of the paragraph) + \value MarginUndefined +*/ + +/*! + Constructs a new style called \a name for the stylesheet \a + parent. + + All properties in Q3StyleSheetItem are initially in the "do not + change" state, except \link Q3StyleSheetItem::DisplayMode display + mode\endlink, which defaults to \c DisplayInline. +*/ +Q3StyleSheetItem::Q3StyleSheetItem(Q3StyleSheet* parent, const QString& name) +{ + d = new Q3StyleSheetItemData; + d->stylename = name.toLower(); + d->sheet = parent; + init(); + if (parent) + parent->insert(this); +} + +/*! + Copy constructor. Constructs a copy of \a other that is not bound + to any style sheet. +*/ +Q3StyleSheetItem::Q3StyleSheetItem(const Q3StyleSheetItem & other) +{ + d = new Q3StyleSheetItemData; + *d = *other.d; +} + + +/*! + Destroys the style. Note that Q3StyleSheetItem objects become + owned by Q3StyleSheet when they are created. +*/ +Q3StyleSheetItem::~Q3StyleSheetItem() +{ + delete d; +} + +/*! + Assignment. Assings a copy of \a other that is not bound to any style sheet. + Unbounds first from previous style sheet. + */ +Q3StyleSheetItem& Q3StyleSheetItem::operator=(const Q3StyleSheetItem& other) +{ + if (&other == this) + return *this; + delete d; + d = new Q3StyleSheetItemData; + *d = *other.d; + return *this; +} + +/*! + Returns the style sheet this item is in. +*/ +Q3StyleSheet* Q3StyleSheetItem::styleSheet() +{ + return d->sheet; +} + +/*! + \overload + + Returns the style sheet this item is in. +*/ +const Q3StyleSheet* Q3StyleSheetItem::styleSheet() const +{ + return d->sheet; +} + +/*! + \internal + Internal initialization + */ +void Q3StyleSheetItem::init() +{ + d->disp = DisplayInline; + + d->fontitalic = Undefined; + d->fontunderline = Undefined; + d->fontstrikeout = Undefined; + d->fontweight = Undefined; + d->fontsize = Undefined; + d->fontsizelog = Undefined; + d->fontsizestep = 0; + d->ncolumns = Undefined; + d->col = QColor(); // !isValid() + d->anchor = false; + d->align = Undefined; + d->valign = VAlignBaseline; + d->margin[0] = Undefined; + d->margin[1] = Undefined; + d->margin[2] = Undefined; + d->margin[3] = Undefined; + d->margin[4] = Undefined; + d->list = ListStyleUndefined; + d->whitespacemode = WhiteSpaceModeUndefined; + d->selfnest = true; + d->lineSpacing = Undefined; +} + +/*! + Returns the name of the style item. +*/ +QString Q3StyleSheetItem::name() const +{ + return d->stylename; +} + +/*! + Returns the \link Q3StyleSheetItem::DisplayMode display + mode\endlink of the style. + + \sa setDisplayMode() +*/ +Q3StyleSheetItem::DisplayMode Q3StyleSheetItem::displayMode() const +{ + return d->disp; +} + +/*! + \enum Q3StyleSheetItem::DisplayMode + + This enum type defines the way adjacent elements are displayed. + + \value DisplayBlock elements are displayed as a rectangular block + (e.g. \c{<p>...</p>}). + + \value DisplayInline elements are displayed in a horizontally + flowing sequence (e.g. \c{<em>...</em>}). + + \value DisplayListItem elements are displayed in a vertical + sequence (e.g. \c{<li>...</li>}). + + \value DisplayNone elements are not displayed at all. + + \omitvalue DisplayModeUndefined +*/ + +/*! + Sets the display mode of the style to \a m. + + \sa displayMode() + */ +void Q3StyleSheetItem::setDisplayMode(DisplayMode m) +{ + d->disp=m; +} + + +/*! + Returns the alignment of this style. Possible values are + Qt::AlignAuto, Qt::AlignLeft, Qt::AlignRight, Qt::AlignCenter or + Qt::AlignJustify. + + \sa setAlignment(), Qt::Alignment +*/ +int Q3StyleSheetItem::alignment() const +{ + return d->align; +} + +/*! + Sets the alignment to \a f. This only makes sense for styles with + a \link Q3StyleSheetItem::DisplayMode display mode\endlink of + DisplayBlock. Possible values are Qt::AlignAuto, Qt::AlignLeft, + Qt::AlignRight, Qt::AlignCenter or Qt::AlignJustify. + + \sa alignment(), displayMode(), Qt::Alignment +*/ +void Q3StyleSheetItem::setAlignment(int f) +{ + d->align = f; +} + + +/*! + Returns the vertical alignment of the style. Possible values are + VAlignBaseline, VAlignSub or VAlignSuper. + + \sa setVerticalAlignment() +*/ +Q3StyleSheetItem::VerticalAlignment Q3StyleSheetItem::verticalAlignment() const +{ + return d->valign; +} + +/*! + \enum Q3StyleSheetItem::VerticalAlignment + + This enum type defines the way elements are aligned vertically. + This is only supported for text elements. + + \value VAlignBaseline align the baseline of the element (or the + bottom, if the element doesn't have a baseline) with the + baseline of the parent + + \value VAlignSub subscript the element + + \value VAlignSuper superscript the element + +*/ + + +/*! + Sets the vertical alignment to \a valign. Possible values are + VAlignBaseline, VAlignSub or VAlignSuper. + + The vertical alignment property is not inherited. + + \sa verticalAlignment() +*/ +void Q3StyleSheetItem::setVerticalAlignment(VerticalAlignment valign) +{ + d->valign = valign; +} + + +/*! + Returns true if the style sets an italic font; otherwise returns + false. + + \sa setFontItalic(), definesFontItalic() +*/ +bool Q3StyleSheetItem::fontItalic() const +{ + return d->fontitalic > 0; +} + +/*! + If \a italic is true sets italic for the style; otherwise sets + upright. + + \sa fontItalic(), definesFontItalic() +*/ +void Q3StyleSheetItem::setFontItalic(bool italic) +{ + d->fontitalic = italic?1:0; +} + +/*! + Returns true if the style defines a font shape; otherwise returns + false. A style does not define any shape until setFontItalic() is + called. + + \sa setFontItalic(), fontItalic() +*/ +bool Q3StyleSheetItem::definesFontItalic() const +{ + return d->fontitalic != Undefined; +} + +/*! + Returns true if the style sets an underlined font; otherwise + returns false. + + \sa setFontUnderline(), definesFontUnderline() +*/ +bool Q3StyleSheetItem::fontUnderline() const +{ + return d->fontunderline > 0; +} + +/*! + If \a underline is true, sets underline for the style; otherwise + sets no underline. + + \sa fontUnderline(), definesFontUnderline() +*/ +void Q3StyleSheetItem::setFontUnderline(bool underline) +{ + d->fontunderline = underline?1:0; +} + +/*! + Returns true if the style defines a setting for the underline + property of the font; otherwise returns false. A style does not + define this until setFontUnderline() is called. + + \sa setFontUnderline(), fontUnderline() +*/ +bool Q3StyleSheetItem::definesFontUnderline() const +{ + return d->fontunderline != Undefined; +} + + +/*! + Returns true if the style sets a strike out font; otherwise + returns false. + + \sa setFontStrikeOut(), definesFontStrikeOut() +*/ +bool Q3StyleSheetItem::fontStrikeOut() const +{ + return d->fontstrikeout > 0; +} + +/*! + If \a strikeOut is true, sets strike out for the style; otherwise + sets no strike out. + + \sa fontStrikeOut(), definesFontStrikeOut() +*/ +void Q3StyleSheetItem::setFontStrikeOut(bool strikeOut) +{ + d->fontstrikeout = strikeOut?1:0; +} + +/*! + Returns true if the style defines a setting for the strikeOut + property of the font; otherwise returns false. A style does not + define this until setFontStrikeOut() is called. + + \sa setFontStrikeOut(), fontStrikeOut() +*/ +bool Q3StyleSheetItem::definesFontStrikeOut() const +{ + return d->fontstrikeout != Undefined; +} + + +/*! + Returns the font weight setting of the style. This is either a + valid QFont::Weight or the value Q3StyleSheetItem::Undefined. + + \sa setFontWeight(), QFont +*/ +int Q3StyleSheetItem::fontWeight() const +{ + return d->fontweight; +} + +/*! + Sets the font weight setting of the style to \a w. Valid values + are those defined by QFont::Weight. + + \sa QFont, fontWeight() +*/ +void Q3StyleSheetItem::setFontWeight(int w) +{ + d->fontweight = w; +} + +/*! + Returns the logical font size setting of the style. This is either + a valid size between 1 and 7 or Q3StyleSheetItem::Undefined. + + \sa setLogicalFontSize(), setLogicalFontSizeStep(), QFont::pointSize(), QFont::setPointSize() +*/ +int Q3StyleSheetItem::logicalFontSize() const +{ + return d->fontsizelog; +} + + +/*! + Sets the logical font size setting of the style to \a s. Valid + logical sizes are 1 to 7. + + \sa logicalFontSize(), QFont::pointSize(), QFont::setPointSize() +*/ +void Q3StyleSheetItem::setLogicalFontSize(int s) +{ + d->fontsizelog = s; +} + +/*! + Returns the logical font size step of this style. + + The default is 0. Tags such as \c big define \c +1; \c small + defines \c -1. + + \sa setLogicalFontSizeStep() +*/ +int Q3StyleSheetItem::logicalFontSizeStep() const +{ + return d->fontsizestep; +} + +/*! + Sets the logical font size step of this style to \a s. + + \sa logicalFontSizeStep() +*/ +void Q3StyleSheetItem::setLogicalFontSizeStep(int s) +{ + d->fontsizestep = s; +} + + + +/*! + Sets the font size setting of the style to \a s points. + + \sa fontSize(), QFont::pointSize(), QFont::setPointSize() +*/ +void Q3StyleSheetItem::setFontSize(int s) +{ + d->fontsize = s; +} + +/*! + Returns the font size setting of the style. This is either a valid + point size or Q3StyleSheetItem::Undefined. + + \sa setFontSize(), QFont::pointSize(), QFont::setPointSize() +*/ +int Q3StyleSheetItem::fontSize() const +{ + return d->fontsize; +} + + +/*! + Returns the style's font family setting. This is either a valid + font family or an empty string if no family has been set. + + \sa setFontFamily(), QFont::family(), QFont::setFamily() +*/ +QString Q3StyleSheetItem::fontFamily() const +{ + return d->fontfamily; +} + +/*! + Sets the font family setting of the style to \a fam. + + \sa fontFamily(), QFont::family(), QFont::setFamily() +*/ +void Q3StyleSheetItem::setFontFamily(const QString& fam) +{ + d->fontfamily = fam; +} + + +/*! + Returns the number of columns for this style. + + \sa setNumberOfColumns(), displayMode(), setDisplayMode() + + */ +int Q3StyleSheetItem::numberOfColumns() const +{ + return d->ncolumns; +} + + +/*! + Sets the number of columns for this style to \a ncols. Elements in the style + are divided into columns. + + This makes sense only if the style uses a block display mode + (see Q3StyleSheetItem::DisplayMode). + + \sa numberOfColumns() + */ +void Q3StyleSheetItem::setNumberOfColumns(int ncols) +{ + if (ncols > 0) + d->ncolumns = ncols; +} + + +/*! + Returns the text color of this style or an invalid color if no + color has been set. + + \sa setColor() QColor::isValid() +*/ +QColor Q3StyleSheetItem::color() const +{ + return d->col; +} + +/*! + Sets the text color of this style to \a c. + + \sa color() +*/ +void Q3StyleSheetItem::setColor(const QColor &c) +{ + d->col = c; +} + +/*! + Returns whether this style is an anchor. + + \sa setAnchor() +*/ +bool Q3StyleSheetItem::isAnchor() const +{ + return d->anchor; +} + +/*! + If \a anc is true, sets this style to be an anchor (hypertext + link); otherwise sets it to not be an anchor. Elements in this + style link to other documents or anchors. + + \sa isAnchor() +*/ +void Q3StyleSheetItem::setAnchor(bool anc) +{ + d->anchor = anc; +} + + +/*! + Returns the whitespace mode. + + \sa setWhiteSpaceMode() WhiteSpaceMode +*/ +Q3StyleSheetItem::WhiteSpaceMode Q3StyleSheetItem::whiteSpaceMode() const +{ + return d->whitespacemode; +} + +/*! + Sets the whitespace mode to \a m. + + \sa WhiteSpaceMode +*/ +void Q3StyleSheetItem::setWhiteSpaceMode(WhiteSpaceMode m) +{ + d->whitespacemode = m; +} + + +/*! + Returns the width of margin \a m in pixels. + + The margin, \a m, can be MarginLeft, MarginRight, + MarginTop, MarginBottom, or MarginFirstLine + + \sa setMargin() Margin +*/ +int Q3StyleSheetItem::margin(Margin m) const +{ + if (m == MarginAll) { + return d->margin[MarginLeft]; + } else if (m == MarginVertical) { + return d->margin[MarginTop]; + } else if (m == MarginHorizontal) { + return d->margin[MarginLeft]; + } else { + return d->margin[m]; + } +} + + +/*! + Sets the width of margin \a m to \a v pixels. + + The margin, \a m, can be \c MarginLeft, \c MarginRight, \c + MarginTop, \c MarginBottom, MarginFirstLine, \c MarginAll, + \c MarginVertical or \c MarginHorizontal. The value \a v must + be >= 0. + + \sa margin() +*/ +void Q3StyleSheetItem::setMargin(Margin m, int v) +{ + if (m == MarginAll) { + d->margin[MarginLeft] = v; + d->margin[MarginRight] = v; + d->margin[MarginTop] = v; + d->margin[MarginBottom] = v; + } else if (m == MarginVertical) { + d->margin[MarginTop] = v; + d->margin[MarginBottom] = v; + } else if (m == MarginHorizontal) { + d->margin[MarginLeft] = v; + d->margin[MarginRight] = v; + } else { + d->margin[m] = v; + } +} + + +/*! + Returns the list style of the style. + + \sa setListStyle() ListStyle + */ +Q3StyleSheetItem::ListStyle Q3StyleSheetItem::listStyle() const +{ + return d->list; +} + +/*! + \enum Q3StyleSheetItem::ListStyle + + This enum type defines how the items in a list are prefixed when + displayed. + + \value ListDisc a filled circle (i.e. a bullet) + \value ListCircle an unfilled circle + \value ListSquare a filled square + \value ListDecimal an integer in base 10: \e 1, \e 2, \e 3, ... + \value ListLowerAlpha a lowercase letter: \e a, \e b, \e c, ... + \value ListUpperAlpha an uppercase letter: \e A, \e B, \e C, ... + \omitvalue ListStyleUndefined +*/ + +/*! + Sets the list style of the style to \a s. + + This is used by nested elements that have a display mode of \c + DisplayListItem. + + \sa listStyle() DisplayMode ListStyle +*/ +void Q3StyleSheetItem::setListStyle(ListStyle s) +{ + d->list=s; +} + + +/*! + Returns a space-separated list of names of styles that may contain + elements of this style. If nothing has been set, contexts() + returns an empty string, which indicates that this style can be + nested everywhere. + + \sa setContexts() +*/ +QString Q3StyleSheetItem::contexts() const +{ + return d->contxt; +} + +/*! + Sets a space-separated list of names of styles that may contain + elements of this style. If \a c is empty, the style can be nested + everywhere. + + \sa contexts() +*/ +void Q3StyleSheetItem::setContexts(const QString& c) +{ + d->contxt = QLatin1Char(' ') + c + QLatin1Char(' '); +} + +/*! + Returns true if this style can be nested into an element of style + \a s; otherwise returns false. + + \sa contexts(), setContexts() +*/ +bool Q3StyleSheetItem::allowedInContext(const Q3StyleSheetItem* s) const +{ + if (d->contxt.isEmpty()) + return true; + return d->contxt.contains(QLatin1Char(' ')+s->name()+QLatin1Char(' ')); +} + + +/*! + Returns true if this style has self-nesting enabled; otherwise + returns false. + + \sa setSelfNesting() +*/ +bool Q3StyleSheetItem::selfNesting() const +{ + return d->selfnest; +} + +/*! + Sets the self-nesting property for this style to \a nesting. + + In order to support "dirty" HTML, paragraphs \c{<p>} and list + items \c{<li>} are not self-nesting. This means that starting a + new paragraph or list item automatically closes the previous one. + + \sa selfNesting() +*/ +void Q3StyleSheetItem::setSelfNesting(bool nesting) +{ + d->selfnest = nesting; +} + +/*! + \internal + Sets the linespacing to be at least \a ls pixels. + + For compatibility with previous Qt releases, small values get + treated differently: If \a ls is smaller than the default font + line spacing in pixels at parse time, the resulting line spacing + is the sum of the default line spacing plus \a ls. We recommend + not relying on this behavior. +*/ + +void Q3StyleSheetItem::setLineSpacing(int ls) +{ + d->lineSpacing = ls; +} + +/*! + Returns the line spacing. +*/ + +int Q3StyleSheetItem::lineSpacing() const +{ + return d->lineSpacing; +} + +//************************************************************************ + + + + +//************************************************************************ + + +/*! + \class Q3StyleSheet + \brief The Q3StyleSheet class is a collection of styles for rich text + rendering and a generator of tags. + + \compat + + By creating Q3StyleSheetItem objects for a style sheet you build a + definition of a set of tags. This definition will be used by the + internal rich text rendering system to parse and display text + documents to which the style sheet applies. Rich text is normally + visualized in a QTextEdit or a QTextBrowser. However, QLabel, + QWhatsThis and QMessageBox also support it, and other classes are + likely to follow. With QSimpleRichText it is possible to use the + rich text renderer for custom widgets as well. + + The default Q3StyleSheet object has the following style bindings, + sorted by structuring bindings, anchors, character style bindings + (i.e. inline styles), special elements such as horizontal lines or + images, and other tags. In addition, rich text supports simple + HTML tables. + + The structuring tags are + \table + \header \i Structuring tags \i Notes + \row \i \c{<qt>}...\c{</qt>} + \i A Qt rich text document. It understands the following + attributes: + \list + \i \c title -- The caption of the document. This attribute is + easily accessible with QTextEdit::documentTitle(). + \i \c type -- The type of the document. The default type is \c + page. It indicates that the document is displayed in a + page of its own. Another style is \c detail, which can be + used to explain certain expressions in more detail in a + few sentences. For \c detail, QTextBrowser will then keep + the current page and display the new document in a small + popup similar to QWhatsThis. Note that links will not work + in documents with \c{<qt type="detail">...</qt>}. + \i \c bgcolor -- The background color, for example \c + bgcolor="yellow" or \c bgcolor="#0000FF". + \i \c background -- The background pixmap, for example \c + background="granite.xpm". The pixmap name will be resolved + by a Q3MimeSourceFactory(). + \i \c text -- The default text color, for example \c text="red". + \i \c link -- The link color, for example \c link="green". + \endlist + \row \i \c{<h1>...</h1>} + \i A top-level heading. + \row \i \c{<h2>...</h2>} + \i A sublevel heading. + \row \i \c{<h3>...</h3>} + \i A sub-sublevel heading. + \row \i \c{<p>...</p>} + \i A left-aligned paragraph. Adjust the alignment with the \c + align attribute. Possible values are \c left, \c right and + \c center. + \row \i \c{<center>...}<br>\c{</center>} + \i A centered paragraph. + \row \i \c{<blockquote>...}<br>\c{</blockquote>} + \i An indented paragraph that is useful for quotes. + \row \i \c{<ul>...</ul>} + \i An unordered list. You can also pass a type argument to + define the bullet style. The default is \c type=disc; + other types are \c circle and \c square. + \row \i \c{<ol>...</ol>} + \i An ordered list. You can also pass a type argument to + define the enumeration label style. The default is \c + type="1"; other types are \c "a" and \c "A". + \row \i \c{<li>...</li>} + \i A list item. This tag can be used only within the context + of \c{<ol>} or \c{<ul>}. + \row \i \c{<pre>...</pre>} + \i For larger chunks of code. Whitespaces in the contents are + preserved. For small bits of code use the inline-style \c + code. + \endtable + + Anchors and links are done with a single tag: + \table + \header \i Anchor tags \i Notes + \row \i \c{<a>...</a>} + \i An anchor or link. + \list + \i A link is created by using an \c href + attribute, for example + <br>\c{<a href="target.qml">Link Text</a>}. Links to + targets within a document are achieved in the same way + as for HTML, e.g. + <br>\c{<a href="target.qml#subtitle">Link Text</a>}. + \i A target is created by using a \c name + attribute, for example + <br>\c{<a name="subtitle"><h2>Sub Title</h2></a>}. + \endlist + \endtable + + The default character style bindings are + \table + \header \i Style tags \i Notes + \row \i \c{<em>...</em>} + \i Emphasized. By default this is the same as \c{<i>...</i>} + (italic). + \row \i \c{<strong>...</strong>} + \i Strong. By default this is the same as \c{<b>...</b>} + (bold). + \row \i \c{<i>...</i>} + \i Italic font style. + \row \i \c{<b>...</b>} + \i Bold font style. + \row \i \c{<u>...</u>} + \i Underlined font style. + \row \i \c{<s>...</s>} + \i Strike out font style. + \row \i \c{<big>...</big>} + \i A larger font size. + \row \i \c{<small>...</small>} + \i A smaller font size. + \row \i \c{<sub>...</sub>} + \i Subscripted text + \row \i \c{<sup>...</sup>} + \i Superscripted text + \row \i \c{<code>...</code>} + \i Indicates code. By default this is the same as + \c{<tt>...</tt>} (typewriter). For larger chunks of code + use the block-tag \c{<}\c{pre>}. + \row \i \c{<tt>...</tt>} + \i Typewriter font style. + \row \i \c{<font>...</font>} + \i Customizes the font size, family and text color. The tag + understands the following attributes: + \list + \i \c color -- The text color, for example \c color="red" or + \c color="#FF0000". + \i \c size -- The logical size of the font. Logical sizes 1 + to 7 are supported. The value may either be absolute + (for example, \c size=3) or relative (\c size=-2). In + the latter case the sizes are simply added. + \i \c face -- The family of the font, for example \c face=times. + \endlist + \endtable + + Special elements are: + \table + \header \i Special tags \i Notes + \row \i \c{<img>} + \i An image. The image name for the mime source factory is + given in the source attribute, for example + \c{<img src="qt.xpm">} The image tag also understands the + attributes \c width and \c height that determine the size + of the image. If the pixmap does not fit the specified + size it will be scaled automatically (by using + QImage::smoothScale()). + + The \c align attribute determines where the image is + placed. By default, an image is placed inline just like a + normal character. Specify \c left or \c right to place the + image at the respective side. + \row \i \c{<hr>} + \i A horizontal line. + \row \i \c{<br>} + \i A line break. + \row \i \c{<nobr>...</nobr>} + \i No break. Prevents word wrap. + \endtable + + In addition, rich text supports simple HTML tables. A table + consists of one or more rows each of which contains one or more + cells. Cells are either data cells or header cells, depending on + their content. Cells which span rows and columns are supported. + + \table + \header \i Table tags \i Notes + \row \i \c{<table>...</table>} + \i A table. Tables support the following attributes: + \list + \i \c bgcolor -- The background color. + \i \c width -- The table width. This is either an absolute + pixel width or a relative percentage of the table's + width, for example \c width=80%. + \i \c border -- The width of the table border. The default is + 0 (= no border). + \i \c cellspacing -- Additional space around the table cells. + The default is 2. + \i \c cellpadding -- Additional space around the contents of + table cells. The default is 1. + \endlist + \row \i \c{<tr>...</tr>} + \i A table row. This is only valid within a \c table. Rows + support the following attribute: + \list + \i \c bgcolor -- The background color. + \endlist + \row \i \c{<th>...</th>} + \i A table header cell. Similar to \c td, but defaults to + center alignment and a bold font. + \row \i \c{<td>...</td>} + \i A table data cell. This is only valid within a \c tr. + Cells support the following attributes: + \list + \i \c bgcolor -- The background color. + \i \c width -- The cell width. This is either an absolute + pixel width or a relative percentage of table's width, + for example \c width=50%. + \i \c colspan -- Specifies how many columns this cell spans. + The default is 1. + \i \c rowspan -- Specifies how many rows this cell spans. The + default is 1. + \i \c align -- Qt::Alignment; possible values are \c left, \c + right, and \c center. The default is \c left. + \i \c valign -- Qt::Vertical alignment; possible values are \c + top, \c middle, and \c bottom. The default is \c middle. + \endlist + \endtable +*/ + +/*! + Creates a style sheet called \a name, with parent \a parent. Like + any QObject it will be deleted when its parent is destroyed (if + the child still exists). + + By default the style sheet has the tag definitions defined above. +*/ +Q3StyleSheet::Q3StyleSheet(QObject *parent, const char *name) + : QObject(parent) +{ + setObjectName(QLatin1String(name)); + init(); +} + +/*! + Destroys the style sheet. All styles inserted into the style sheet + will be deleted. +*/ +Q3StyleSheet::~Q3StyleSheet() +{ + QHash<QString, Q3StyleSheetItem *>::iterator it = styles.begin(); + while (it != styles.end()) { + delete it.value(); + ++it; + } +} + +/*! + \internal + Initialized the style sheet to the basic Qt style. +*/ +void Q3StyleSheet::init() +{ + nullstyle = new Q3StyleSheetItem(this, QString::fromLatin1("")); + + Q3StyleSheetItem *style; + + style = new Q3StyleSheetItem(this, QLatin1String("qml")); // compatibility + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("qt")); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("a")); + style->setAnchor(true); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("em")); + style->setFontItalic(true); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("i")); + style->setFontItalic(true); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("big")); + style->setLogicalFontSizeStep(1); + style = new Q3StyleSheetItem(this, QString::fromLatin1("large")); // compatibility + style->setLogicalFontSizeStep(1); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("small")); + style->setLogicalFontSizeStep(-1); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("strong")); + style->setFontWeight(QFont::Bold); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("b")); + style->setFontWeight(QFont::Bold); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("h1")); + style->setFontWeight(QFont::Bold); + style->setLogicalFontSize(6); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + style-> setMargin(Q3StyleSheetItem::MarginTop, 18); + style-> setMargin(Q3StyleSheetItem::MarginBottom, 12); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("h2")); + style->setFontWeight(QFont::Bold); + style->setLogicalFontSize(5); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + style-> setMargin(Q3StyleSheetItem::MarginTop, 16); + style-> setMargin(Q3StyleSheetItem::MarginBottom, 12); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("h3")); + style->setFontWeight(QFont::Bold); + style->setLogicalFontSize(4); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + style-> setMargin(Q3StyleSheetItem::MarginTop, 14); + style-> setMargin(Q3StyleSheetItem::MarginBottom, 12); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("h4")); + style->setFontWeight(QFont::Bold); + style->setLogicalFontSize(3); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + style-> setMargin(Q3StyleSheetItem::MarginVertical, 12); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("h5")); + style->setFontWeight(QFont::Bold); + style->setLogicalFontSize(2); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + style-> setMargin(Q3StyleSheetItem::MarginTop, 12); + style-> setMargin(Q3StyleSheetItem::MarginBottom, 4); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("p")); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + style-> setMargin(Q3StyleSheetItem::MarginVertical, 12); + style->setSelfNesting(false); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("center")); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + style->setAlignment(Qt::AlignCenter); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("twocolumn")); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + style->setNumberOfColumns(2); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("multicol")); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + (void) new Q3StyleSheetItem(this, QString::fromLatin1("font")); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("ul")); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + style->setListStyle(Q3StyleSheetItem::ListDisc); + style-> setMargin(Q3StyleSheetItem::MarginVertical, 12); + style->setMargin(Q3StyleSheetItem::MarginLeft, 40); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("ol")); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + style->setListStyle(Q3StyleSheetItem::ListDecimal); + style-> setMargin(Q3StyleSheetItem::MarginVertical, 12); + style->setMargin(Q3StyleSheetItem::MarginLeft, 40); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("li")); + style->setDisplayMode(Q3StyleSheetItem::DisplayListItem); + style->setSelfNesting(false); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("code")); + style->setFontFamily(QString::fromLatin1("Courier New,courier")); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("tt")); + style->setFontFamily(QString::fromLatin1("Courier New,courier")); + + new Q3StyleSheetItem(this, QString::fromLatin1("img")); + new Q3StyleSheetItem(this, QString::fromLatin1("br")); + new Q3StyleSheetItem(this, QString::fromLatin1("hr")); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("sub")); + style->setVerticalAlignment(Q3StyleSheetItem::VAlignSub); + style = new Q3StyleSheetItem(this, QString::fromLatin1("sup")); + style->setVerticalAlignment(Q3StyleSheetItem::VAlignSuper); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("pre")); + style->setFontFamily(QString::fromLatin1("Courier New,courier")); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + style->setWhiteSpaceMode(Q3StyleSheetItem::WhiteSpacePre); + style-> setMargin(Q3StyleSheetItem::MarginVertical, 12); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("blockquote")); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + style->setMargin(Q3StyleSheetItem::MarginHorizontal, 40); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("head")); + style->setDisplayMode(Q3StyleSheetItem::DisplayNone); + style = new Q3StyleSheetItem(this, QString::fromLatin1("body")); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + style = new Q3StyleSheetItem(this, QString::fromLatin1("div")); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock) ; + style = new Q3StyleSheetItem(this, QString::fromLatin1("span")); + style = new Q3StyleSheetItem(this, QString::fromLatin1("dl")); + style-> setMargin(Q3StyleSheetItem::MarginVertical, 8); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + style = new Q3StyleSheetItem(this, QString::fromLatin1("dt")); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + style->setContexts(QString::fromLatin1("dl")); + style = new Q3StyleSheetItem(this, QString::fromLatin1("dd")); + style->setDisplayMode(Q3StyleSheetItem::DisplayBlock); + style->setMargin(Q3StyleSheetItem::MarginLeft, 30); + style->setContexts(QString::fromLatin1("dt dl")); + style = new Q3StyleSheetItem(this, QString::fromLatin1("u")); + style->setFontUnderline(true); + style = new Q3StyleSheetItem(this, QString::fromLatin1("s")); + style->setFontStrikeOut(true); + style = new Q3StyleSheetItem(this, QString::fromLatin1("nobr")); + style->setWhiteSpaceMode(Q3StyleSheetItem::WhiteSpaceNoWrap); + + // compatibily with some minor 3.0.x Qt versions that had an + // undocumented <wsp> tag. ### Remove 3.1 + style = new Q3StyleSheetItem(this, QString::fromLatin1("wsp")); + style->setWhiteSpaceMode(Q3StyleSheetItem::WhiteSpacePre); + + // tables + style = new Q3StyleSheetItem(this, QString::fromLatin1("table")); + style = new Q3StyleSheetItem(this, QString::fromLatin1("tr")); + style->setContexts(QString::fromLatin1("table")); + style = new Q3StyleSheetItem(this, QString::fromLatin1("td")); + style->setContexts(QString::fromLatin1("tr")); + style = new Q3StyleSheetItem(this, QString::fromLatin1("th")); + style->setFontWeight(QFont::Bold); + style->setAlignment(Qt::AlignCenter); + style->setContexts(QString::fromLatin1("tr")); + + style = new Q3StyleSheetItem(this, QString::fromLatin1("html")); +} + + + +static Q3StyleSheet* defaultsheet = 0; +static Q3SingleCleanupHandler<Q3StyleSheet> qt_cleanup_stylesheet; + +/*! + Returns the application-wide default style sheet. This style sheet + is used by rich text rendering classes such as QSimpleRichText, + QWhatsThis and QMessageBox to define the rendering style and + available tags within rich text documents. It also serves as the + initial style sheet for the more complex render widgets, QTextEdit + and QTextBrowser. + + \sa setDefaultSheet() +*/ +Q3StyleSheet* Q3StyleSheet::defaultSheet() +{ + if (!defaultsheet) { + defaultsheet = new Q3StyleSheet(); + qt_cleanup_stylesheet.set(&defaultsheet); + } + return defaultsheet; +} + +/*! + Sets the application-wide default style sheet to \a sheet, + deleting any style sheet previously set. The ownership is + transferred to Q3StyleSheet. + + \sa defaultSheet() +*/ +void Q3StyleSheet::setDefaultSheet(Q3StyleSheet* sheet) +{ + if (defaultsheet != sheet) { + if (defaultsheet) + qt_cleanup_stylesheet.reset(); + delete defaultsheet; + } + defaultsheet = sheet; + if (defaultsheet) + qt_cleanup_stylesheet.set(&defaultsheet); +} + +/*!\internal + Inserts \a style. Any tags generated after this time will be + bound to this style. Note that \a style becomes owned by the + style sheet and will be deleted when the style sheet is destroyed. +*/ +void Q3StyleSheet::insert(Q3StyleSheetItem* style) +{ + styles.insert(style->name(), style); +} + + +/*! + Returns the style called \a name or 0 if there is no such style. +*/ +Q3StyleSheetItem* Q3StyleSheet::item(const QString& name) +{ + if (name.isNull()) + return 0; + return styles.value(name); +} + +/*! + \overload + + Returns the style called \a name or 0 if there is no such style + (const version) +*/ +const Q3StyleSheetItem* Q3StyleSheet::item(const QString& name) const +{ + if (name.isNull()) + return 0; + return styles.value(name); +} + +/*! Auxiliary function. Converts the plain text string \a plain to a + rich text formatted paragraph while preserving most of its look. + + \a mode defines the whitespace mode. Possible values are \c + Q3StyleSheetItem::WhiteSpacePre (no wrapping, all whitespaces + preserved) and Q3StyleSheetItem::WhiteSpaceNormal (wrapping, + simplified whitespaces). + + \sa escape() +*/ +QString Q3StyleSheet::convertFromPlainText(const QString& plain, Q3StyleSheetItem::WhiteSpaceMode mode) +{ + return Qt::convertFromPlainText(plain, Qt::WhiteSpaceMode(mode)); +} + +/*! + Auxiliary function. Converts the plain text string \a plain to a + rich text formatted string with any HTML meta-characters escaped. + + \sa convertFromPlainText() +*/ +QString Q3StyleSheet::escape(const QString& plain) +{ + return Qt::escape(plain); +} + +// Must doc this enum somewhere, and it is logically related to Q3StyleSheet + +/*! + Returns true if the string \a text is likely to be rich text; + otherwise returns false. + + This function uses a fast and therefore simple heuristic. It + mainly checks whether there is something that looks like a tag + before the first line break. Although the result may be correct + for common cases, there is no guarantee. +*/ +bool Q3StyleSheet::mightBeRichText(const QString& text) +{ + return Qt::mightBeRichText(text); +} + + +/*! + \fn void Q3StyleSheet::error(const QString& msg) const + + This virtual function is called when an error occurs when + processing rich text. Reimplement it if you need to catch error + messages. + + Errors might occur if some rich text strings contain tags that are + not understood by the stylesheet, if some tags are nested + incorrectly, or if tags are not closed properly. + + \a msg is the error message. +*/ +void Q3StyleSheet::error(const QString&) const +{ +} + + +/*! + Scales the font \a font to the appropriate physical point size + corresponding to the logical font size \a logicalSize. + + When calling this function, \a font has a point size corresponding + to the logical font size 3. + + Logical font sizes range from 1 to 7, with 1 being the smallest. + + \sa Q3StyleSheetItem::logicalFontSize(), Q3StyleSheetItem::logicalFontSizeStep(), QFont::setPointSize() + */ +void Q3StyleSheet::scaleFont(QFont& font, int logicalSize) const +{ + if (logicalSize < 1) + logicalSize = 1; + if (logicalSize > 7) + logicalSize = 7; + int baseSize = font.pointSize(); + bool pixel = false; + if (baseSize == -1) { + baseSize = font.pixelSize(); + pixel = true; + } + int s; + switch (logicalSize) { + case 1: + s = 7*baseSize/10; + break; + case 2: + s = (8 * baseSize) / 10; + break; + case 4: + s = (12 * baseSize) / 10; + break; + case 5: + s = (15 * baseSize) / 10; + break; + case 6: + s = 2 * baseSize; + break; + case 7: + s = (24 * baseSize) / 10; + break; + default: + s = baseSize; + } + if (pixel) + font.setPixelSize(qMax(1, s)); + else + font.setPointSize(qMax(1, s)); +} + +QT_END_NAMESPACE + +#endif // QT_NO_RICHTEXT diff --git a/src/qt3support/text/q3stylesheet.h b/src/qt3support/text/q3stylesheet.h new file mode 100644 index 0000000..d13b3e9 --- /dev/null +++ b/src/qt3support/text/q3stylesheet.h @@ -0,0 +1,235 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3STYLESHEET_H +#define Q3STYLESHEET_H + +#include <QtCore/qstring.h> +#include <QtCore/qlist.h> +#include <QtCore/qhash.h> +#include <QtCore/qobject.h> +#include <QtGui/qcolor.h> +#include <QtGui/qfont.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3SupportLight) + +#ifndef QT_NO_RICHTEXT + +class Q3StyleSheet; +class Q3TextDocument; +template<class Key, class T> class QMap; +class Q3StyleSheetItemData; + +class Q_COMPAT_EXPORT Q3StyleSheetItem +{ +public: + Q3StyleSheetItem(Q3StyleSheet* parent, const QString& name); + Q3StyleSheetItem(const Q3StyleSheetItem &); + ~Q3StyleSheetItem(); + + Q3StyleSheetItem& operator=(const Q3StyleSheetItem& other); + + QString name() const; + + Q3StyleSheet* styleSheet(); + const Q3StyleSheet* styleSheet() const; + + enum AdditionalStyleValues { Undefined = -1 }; + + enum DisplayMode { + DisplayBlock, + DisplayInline, + DisplayListItem, + DisplayNone, + DisplayModeUndefined = -1 + }; + + DisplayMode displayMode() const; + void setDisplayMode(DisplayMode m); + + int alignment() const; + void setAlignment(int f); + + enum VerticalAlignment { + VAlignBaseline, + VAlignSub, + VAlignSuper + }; + + VerticalAlignment verticalAlignment() const; + void setVerticalAlignment(VerticalAlignment valign); + + int fontWeight() const; + void setFontWeight(int w); + + int logicalFontSize() const; + void setLogicalFontSize(int s); + + int logicalFontSizeStep() const; + void setLogicalFontSizeStep(int s); + + int fontSize() const; + void setFontSize(int s); + + QString fontFamily() const; + void setFontFamily(const QString&); + + int numberOfColumns() const; + void setNumberOfColumns(int ncols); + + QColor color() const; + void setColor(const QColor &); + + bool fontItalic() const; + void setFontItalic(bool); + bool definesFontItalic() const; + + bool fontUnderline() const; + void setFontUnderline(bool); + bool definesFontUnderline() const; + + bool fontStrikeOut() const; + void setFontStrikeOut(bool); + bool definesFontStrikeOut() const; + + bool isAnchor() const; + void setAnchor(bool anc); + + enum WhiteSpaceMode { + WhiteSpaceNormal, + WhiteSpacePre, + WhiteSpaceNoWrap, + WhiteSpaceModeUndefined = -1 + }; + WhiteSpaceMode whiteSpaceMode() const; + void setWhiteSpaceMode(WhiteSpaceMode m); + + enum Margin { + MarginLeft, + MarginRight, + MarginTop, + MarginBottom, + MarginFirstLine, + MarginAll, + MarginVertical, + MarginHorizontal, + MarginUndefined = -1 + }; + + int margin(Margin m) const; + void setMargin(Margin, int); + + enum ListStyle { + ListDisc, + ListCircle, + ListSquare, + ListDecimal, + ListLowerAlpha, + ListUpperAlpha, + ListStyleUndefined = -1 + }; + + ListStyle listStyle() const; + void setListStyle(ListStyle); + + QString contexts() const; + void setContexts(const QString&); + bool allowedInContext(const Q3StyleSheetItem*) const; + + bool selfNesting() const; + void setSelfNesting(bool); + + void setLineSpacing(int ls); + int lineSpacing() const; + +private: + void init(); + Q3StyleSheetItemData* d; +}; + +#ifndef QT_NO_TEXTCUSTOMITEM +class Q3TextCustomItem; +#endif + +class Q_COMPAT_EXPORT Q3StyleSheet : public QObject +{ + Q_OBJECT +public: + Q3StyleSheet(QObject *parent=0, const char *name=0); + virtual ~Q3StyleSheet(); + + static Q3StyleSheet* defaultSheet(); + static void setDefaultSheet(Q3StyleSheet*); + + + Q3StyleSheetItem* item(const QString& name); + const Q3StyleSheetItem* item(const QString& name) const; + + void insert(Q3StyleSheetItem* item); + + static QString escape(const QString&); + static QString convertFromPlainText(const QString&, + Q3StyleSheetItem::WhiteSpaceMode mode = Q3StyleSheetItem::WhiteSpacePre); + static bool mightBeRichText(const QString&); + + virtual void scaleFont(QFont& font, int logicalSize) const; + + virtual void error(const QString&) const; + +private: + Q_DISABLE_COPY(Q3StyleSheet) + + void init(); + QHash<QString, Q3StyleSheetItem *> styles; + Q3StyleSheetItem* nullstyle; +}; + +#endif // QT_NO_RICHTEXT + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3STYLESHEET_H diff --git a/src/qt3support/text/q3syntaxhighlighter.cpp b/src/qt3support/text/q3syntaxhighlighter.cpp new file mode 100644 index 0000000..b81a529 --- /dev/null +++ b/src/qt3support/text/q3syntaxhighlighter.cpp @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3syntaxhighlighter.h" +#include "q3syntaxhighlighter_p.h" + +#ifndef QT_NO_SYNTAXHIGHLIGHTER +#include "q3textedit.h" +#include "qtimer.h" + +QT_BEGIN_NAMESPACE + +/*! + \class Q3SyntaxHighlighter + \brief The Q3SyntaxHighlighter class is a base class for + implementing Q3TextEdit syntax highlighters. + + \compat + + A syntax highligher automatically highlights parts of the text in + a Q3TextEdit. Syntax highlighters are often used when the user is + entering text in a specific format (for example, source code) and + help the user to read the text and identify syntax errors. + + To provide your own syntax highlighting for Q3TextEdit, you must + subclass Q3SyntaxHighlighter and reimplement highlightParagraph(). + + When you create an instance of your Q3SyntaxHighlighter subclass, + pass it the Q3TextEdit that you want the syntax highlighting to be + applied to. After this your highlightParagraph() function will be + called automatically whenever necessary. Use your + highlightParagraph() function to apply formatting (e.g. setting + the font and color) to the text that is passed to it. +*/ + +/*! + Constructs the Q3SyntaxHighlighter and installs it on \a textEdit. + Ownership of the Q3SyntaxHighlighter is transferred to the \a + textEdit +*/ + +Q3SyntaxHighlighter::Q3SyntaxHighlighter(Q3TextEdit *textEdit) + : para(0), edit(textEdit), d(new Q3SyntaxHighlighterPrivate) +{ + textEdit->document()->setPreProcessor(new Q3SyntaxHighlighterInternal(this)); + textEdit->document()->invalidate(); + QTimer::singleShot(0, textEdit->viewport(), SLOT(update())); +} + +/*! + Destructor. Uninstalls this syntax highlighter from the textEdit() +*/ + +Q3SyntaxHighlighter::~Q3SyntaxHighlighter() +{ + delete d; + textEdit()->document()->setPreProcessor(0); +} + +/*! + \fn int Q3SyntaxHighlighter::highlightParagraph(const QString &text, int endStateOfLastPara) + + This function is called when necessary by the rich text engine, + i.e. on paragraphs which have changed. + + In your reimplementation you should parse the paragraph's \a text + and call setFormat() as often as necessary to apply any font and + color changes that you require. Your function must return a value + which indicates the paragraph's end state: see below. + + Some syntaxes can have constructs that span paragraphs. For + example, a C++ syntax highlighter should be able to cope with + \c{/}\c{*...*}\c{/} comments that span paragraphs. To deal + with these cases it is necessary to know the end state of the + previous paragraph (e.g. "in comment"). + + If your syntax does not have paragraph spanning constructs, simply + ignore the \a endStateOfLastPara parameter and always return 0. + + Whenever highlightParagraph() is called it is passed a value for + \a endStateOfLastPara. For the very first paragraph this value is + always -2. For any other paragraph the value is the value returned + by the most recent highlightParagraph() call that applied to the + preceding paragraph. + + The value you return is up to you. We recommend only returning 0 + (to signify that this paragraph's syntax highlighting does not + affect the following paragraph), or a positive integer (to signify + that this paragraph has ended in the middle of a paragraph + spanning construct). + + To find out which paragraph is highlighted, call + currentParagraph(). + + For example, if you're writing a simple C++ syntax highlighter, + you might designate 1 to signify "in comment". For a paragraph + that ended in the middle of a comment you'd return 1, and for + other paragraphs you'd return 0. In your parsing code if \a + endStateOfLastPara was 1, you would highlight the text as a C++ + comment until you reached the closing \c{*}\c{/}. +*/ + +/*! + This function is applied to the syntax highlighter's current + paragraph (the text of which is passed to the highlightParagraph() + function). + + The specified \a font and \a color are applied to the text from + position \a start for \a count characters. (If \a count is 0, + nothing is done.) +*/ + +void Q3SyntaxHighlighter::setFormat(int start, int count, const QFont &font, const QColor &color) +{ + if (!para || count <= 0) + return; + Q3TextFormat *f = 0; + f = para->document()->formatCollection()->format(font, color); + para->setFormat(start, count, f); + f->removeRef(); +} + +/*! \overload */ + +void Q3SyntaxHighlighter::setFormat(int start, int count, const QColor &color) +{ + if (!para || count <= 0) + return; + Q3TextFormat *f = 0; + QFont fnt = textEdit()->QWidget::font(); + f = para->document()->formatCollection()->format(fnt, color); + para->setFormat(start, count, f); + f->removeRef(); +} + +/*! \overload */ + +void Q3SyntaxHighlighter::setFormat(int start, int count, const QFont &font) +{ + if (!para || count <= 0) + return; + Q3TextFormat *f = 0; + QColor c = textEdit()->viewport()->palette().color(textEdit()->viewport()->foregroundRole()); + f = para->document()->formatCollection()->format(font, c); + para->setFormat(start, count, f); + f->removeRef(); +} + +/*! + \fn Q3TextEdit *Q3SyntaxHighlighter::textEdit() const + + Returns the Q3TextEdit on which this syntax highlighter is + installed +*/ + +/*! Redoes the highlighting of the whole document. +*/ + +void Q3SyntaxHighlighter::rehighlight() +{ + Q3TextParagraph *s = edit->document()->firstParagraph(); + while (s) { + s->invalidate(0); + s->state = -1; + s->needPreProcess = true; + s = s->next(); + } + edit->repaintContents(); +} + +/*! + Returns the id of the paragraph which is highlighted, or -1 of no + paragraph is currently highlighted. + + Usually this function is called from within highlightParagraph(). +*/ + +int Q3SyntaxHighlighter::currentParagraph() const +{ + return d->currentParagraph; +} + +QT_END_NAMESPACE + +#endif diff --git a/src/qt3support/text/q3syntaxhighlighter.h b/src/qt3support/text/q3syntaxhighlighter.h new file mode 100644 index 0000000..432ce74 --- /dev/null +++ b/src/qt3support/text/q3syntaxhighlighter.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3SYNTAXHIGHLIGHTER_H +#define Q3SYNTAXHIGHLIGHTER_H + +#include <QtGui/qfont.h> +#include <QtGui/qcolor.h> +#include <QtCore/qstring.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3SupportLight) + +class Q3TextEdit; +class Q3SyntaxHighlighterInternal; +class Q3SyntaxHighlighterPrivate; +class Q3TextParagraph; + +class Q_COMPAT_EXPORT Q3SyntaxHighlighter +{ + friend class Q3SyntaxHighlighterInternal; + +public: + Q3SyntaxHighlighter(Q3TextEdit *textEdit); + virtual ~Q3SyntaxHighlighter(); + + virtual int highlightParagraph(const QString &text, int endStateOfLastPara) = 0; + + void setFormat(int start, int count, const QFont &font, const QColor &color); + void setFormat(int start, int count, const QColor &color); + void setFormat(int start, int count, const QFont &font); + Q3TextEdit *textEdit() const { return edit; } + + void rehighlight(); + + int currentParagraph() const; + +private: + Q3TextParagraph *para; + Q3TextEdit *edit; + Q3SyntaxHighlighterPrivate *d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3SYNTAXHIGHLIGHTER_H diff --git a/src/qt3support/text/q3syntaxhighlighter_p.h b/src/qt3support/text/q3syntaxhighlighter_p.h new file mode 100644 index 0000000..73820c7 --- /dev/null +++ b/src/qt3support/text/q3syntaxhighlighter_p.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3SYNTAXHIGHLIGHTER_P_H +#define Q3SYNTAXHIGHLIGHTER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QT_NO_SYNTAXHIGHLIGHTER +#include "q3syntaxhighlighter.h" +#include "private/q3richtext_p.h" + +QT_BEGIN_NAMESPACE + +class Q3SyntaxHighlighterPrivate +{ +public: + Q3SyntaxHighlighterPrivate() : + currentParagraph(-1) + {} + + int currentParagraph; +}; + +class Q3SyntaxHighlighterInternal : public Q3TextPreProcessor +{ +public: + Q3SyntaxHighlighterInternal(Q3SyntaxHighlighter *h) : highlighter(h) {} + void process(Q3TextDocument *doc, Q3TextParagraph *p, int, bool invalidate) { + if (p->prev() && p->prev()->endState() == -1) + process(doc, p->prev(), 0, false); + + highlighter->para = p; + QString text = p->string()->toString(); + int endState = p->prev() ? p->prev()->endState() : -2; + int oldEndState = p->endState(); + highlighter->d->currentParagraph = p->paragId(); + p->setEndState(highlighter->highlightParagraph(text, endState)); + highlighter->d->currentParagraph = -1; + highlighter->para = 0; + + p->setFirstPreProcess(false); + Q3TextParagraph *op = p; + p = p->next(); + if ((!!oldEndState || !!op->endState()) && oldEndState != op->endState() && + invalidate && p && !p->firstPreProcess() && p->endState() != -1) { + while (p) { + if (p->endState() == -1) + return; + p->setEndState(-1); + p = p->next(); + } + } + } + Q3TextFormat *format(int) { return 0; } + +private: + Q3SyntaxHighlighter *highlighter; + + friend class Q3TextEdit; +}; + +#endif // QT_NO_SYNTAXHIGHLIGHTER + +QT_END_NAMESPACE + +#endif // Q3SYNTAXHIGHLIGHTER_P_H diff --git a/src/qt3support/text/q3textbrowser.cpp b/src/qt3support/text/q3textbrowser.cpp new file mode 100644 index 0000000..8f4280f --- /dev/null +++ b/src/qt3support/text/q3textbrowser.cpp @@ -0,0 +1,526 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3textbrowser.h" +#ifndef QT_NO_TEXTBROWSER +#include <private/q3richtext_p.h> + +#include "qevent.h" +#include "qdesktopwidget.h" +#include "qapplication.h" +#include "qlayout.h" +#include "qpainter.h" +#include "qstack.h" +#include "stdio.h" +#include "qfile.h" +#include "qtextstream.h" +#include "qbitmap.h" +#include "qtimer.h" +#include "qimage.h" +#include "q3simplerichtext.h" +#include "q3dragobject.h" +#include "qurl.h" +#include "qcursor.h" + +QT_BEGIN_NAMESPACE + +/*! + \class Q3TextBrowser + \brief The Q3TextBrowser class provides a rich text browser with hypertext navigation. + + \compat + + This class extends Q3TextEdit (in read-only mode), adding some + navigation functionality so that users can follow links in + hypertext documents. The contents of Q3TextEdit is set with + setText(), but Q3TextBrowser has an additional function, + setSource(), which makes it possible to set the text to a named + document. The name is looked up in the text view's mime source + factory. If a document name ends with an anchor (for example, "\c + #anchor"), the text browser automatically scrolls to that position + (using scrollToAnchor()). When the user clicks on a hyperlink, the + browser will call setSource() itself, with the link's \c href + value as argument. You can track the current source by connetion + to the sourceChanged() signal. + + Q3TextBrowser provides backward() and forward() slots which you can + use to implement Back and Forward buttons. The home() slot sets + the text to the very first document displayed. The linkClicked() + signal is emitted when the user clicks a link. + + By using Q3TextEdit::setMimeSourceFactory() you can provide your + own subclass of Q3MimeSourceFactory. This makes it possible to + access data from anywhere, for example from a network or from a + database. See Q3MimeSourceFactory::data() for details. + + If you intend using the mime factory to read the data directly + from the file system, you may have to specify the encoding for the + file extension you are using. For example: + \snippet doc/src/snippets/code/src_qt3support_text_q3textbrowser.cpp 0 + This is to ensure that the factory is able to resolve the document + names. + + Q3TextBrowser interprets the tags it processes in accordance with + the default style sheet. Change the style sheet with + \l{setStyleSheet()}; see QStyleSheet for details. + + If you want to provide your users with editable rich text use + Q3TextEdit. If you want a text browser without hypertext navigation + use Q3TextEdit, and use Q3TextEdit::setReadOnly() to disable + editing. If you just need to display a small piece of rich text + use QSimpleRichText or QLabel. +*/ + +class Q3TextBrowserData +{ +public: + Q3TextBrowserData():textOrSourceChanged(false) {} + + QStack<QString> stack; + QStack<QString> forwardStack; + QString home; + QString curmain; + QString curmark; + + /*flag necessary to give the linkClicked() signal some meaningful + semantics when somebody connected to it calls setText() or + setSource() */ + bool textOrSourceChanged; +}; + + +/*! + Constructs an empty Q3TextBrowser called \a name, with parent \a + parent. +*/ +Q3TextBrowser::Q3TextBrowser(QWidget *parent, const char *name) + : Q3TextEdit(parent, name) +{ + setReadOnly(true); + d = new Q3TextBrowserData; + + viewport()->setMouseTracking(true); +} + +/*! + \internal +*/ +Q3TextBrowser::~Q3TextBrowser() +{ + delete d; +} + + +/*! + \property Q3TextBrowser::source + \brief the name of the displayed document. + + This is a an empty string if no document is displayed or if the + source is unknown. + + Setting this property uses the mimeSourceFactory() to lookup the + named document. It also checks for optional anchors and scrolls + the document accordingly. + + If the first tag in the document is \c{<qt type=detail>}, the + document is displayed as a popup rather than as new document in + the browser window itself. Otherwise, the document is displayed + normally in the text browser with the text set to the contents of + the named document with setText(). + + If you are using the filesystem access capabilities of the mime + source factory, you must ensure that the factory knows about the + encoding of specified files; otherwise no data will be available. + The default factory handles a couple of common file extensions + such as \c *.html and \c *.txt with reasonable defaults. See + Q3MimeSourceFactory::data() for details. +*/ + +QString Q3TextBrowser::source() const +{ + if (d->stack.isEmpty()) + return QString(); + else + return d->stack.top(); +} + +/*! + Reloads the current set source. +*/ + +void Q3TextBrowser::reload() +{ + QString s = d->curmain; + d->curmain = QLatin1String(""); + setSource(s); +} + + +void Q3TextBrowser::setSource(const QString& name) +{ +#ifndef QT_NO_CURSOR + if (isVisible()) + qApp->setOverrideCursor(Qt::WaitCursor); +#endif + d->textOrSourceChanged = true; + QString source = name; + QString mark; + int hash = name.indexOf(QLatin1Char('#')); + if (hash != -1) { + source = name.left(hash); + mark = name.mid(hash+1); + } + + if (source.left(5) == QLatin1String("file:")) + source = source.mid(6); + + QString url = mimeSourceFactory()->makeAbsolute(source, context()); + QString txt; + bool dosettext = false; + + if (!source.isEmpty() && url != d->curmain) { + const QMimeSource* m = + mimeSourceFactory()->data(source, context()); + if (!m){ + qWarning("Q3TextBrowser: no mimesource for %s", source.latin1()); + } + else { + if (!Q3TextDrag::decode(m, txt)) { + qWarning("Q3TextBrowser: cannot decode %s", source.latin1()); + } + } + if (isVisible()) { + QString firstTag = txt.left(txt.indexOf(QLatin1Char('>')) + 1); + if (firstTag.left(3) == QLatin1String("<qt") && firstTag.contains(QLatin1String("type")) && firstTag.contains(QLatin1String("detail"))) { + popupDetail(txt, QCursor::pos()); +#ifndef QT_NO_CURSOR + qApp->restoreOverrideCursor(); +#endif + return; + } + } + + d->curmain = url; + dosettext = true; + } + + d->curmark = mark; + + if (!mark.isEmpty()) { + url += QLatin1Char('#'); + url += mark; + } + if (d->home.count() == 0) + d->home = url; + + if (d->stack.isEmpty() || d->stack.top() != url) + d->stack.push(url); + + int stackCount = (int)d->stack.count(); + if (d->stack.top() == url) + stackCount--; + emit backwardAvailable(stackCount > 0); + stackCount = (int)d->forwardStack.count(); + if (d->forwardStack.isEmpty() || d->forwardStack.top() == url) + stackCount--; + emit forwardAvailable(stackCount > 0); + + if (dosettext) + Q3TextEdit::setText(txt, url); + + if (!mark.isEmpty()) + scrollToAnchor(mark); + else + setContentsPos(0, 0); + +#ifndef QT_NO_CURSOR + if (isVisible()) + qApp->restoreOverrideCursor(); +#endif + + emit sourceChanged(url); +} + +/*! + \fn void Q3TextBrowser::backwardAvailable(bool available) + + This signal is emitted when the availability of backward() + changes. \a available is false when the user is at home(); + otherwise it is true. +*/ + +/*! + \fn void Q3TextBrowser::forwardAvailable(bool available) + + This signal is emitted when the availability of forward() changes. + \a available is true after the user navigates backward() and false + when the user navigates or goes forward(). +*/ + +/*! + \fn void Q3TextBrowser::sourceChanged(const QString& src) + + This signal is emitted when the mime source has changed, \a src + being the new source. + + Source changes happen both programmatically when calling + setSource(), forward(), backword() or home() or when the user + clicks on links or presses the equivalent key sequences. +*/ + +/*! \fn void Q3TextBrowser::highlighted (const QString &link) + + This signal is emitted when the user has selected but not + activated a link in the document. \a link is the value of the \c + href i.e. the name of the target document. +*/ + +/*! + \fn void Q3TextBrowser::linkClicked(const QString& link) + + This signal is emitted when the user clicks a link. The \a link is + the value of the \c href i.e. the name of the target document. + + The \a link will be the absolute location of the document, based + on the value of the anchor's href tag and the current context of + the document. + + \sa anchorClicked() +*/ + +/*! + \fn void Q3TextBrowser::anchorClicked(const QString& name, const QString &link) + + This signal is emitted when the user clicks an anchor. The \a link is + the value of the \c href i.e. the name of the target document. The \a name + is the name of the anchor. + + \sa linkClicked() +*/ + +/*! + Changes the document displayed to the previous document in the + list of documents built by navigating links. Does nothing if there + is no previous document. + + \sa forward(), backwardAvailable() +*/ +void Q3TextBrowser::backward() +{ + if (d->stack.count() <= 1) + return; + d->forwardStack.push(d->stack.pop()); + setSource(d->stack.pop()); + emit forwardAvailable(true); +} + +/*! + Changes the document displayed to the next document in the list of + documents built by navigating links. Does nothing if there is no + next document. + + \sa backward(), forwardAvailable() +*/ +void Q3TextBrowser::forward() +{ + if (d->forwardStack.isEmpty()) + return; + setSource(d->forwardStack.pop()); + emit forwardAvailable(!d->forwardStack.isEmpty()); +} + +/*! + Changes the document displayed to be the first document the + browser displayed. +*/ +void Q3TextBrowser::home() +{ + if (!d->home.isNull()) + setSource(d->home); +} + +/*! + The event \a e is used to provide the following keyboard shortcuts: + \table + \header \i Keypress \i Action + \row \i Alt+Left Arrow \i \l backward() + \row \i Alt+Right Arrow \i \l forward() + \row \i Alt+Up Arrow \i \l home() + \endtable +*/ +void Q3TextBrowser::keyPressEvent(QKeyEvent * e) +{ + if (e->state() & Qt::AltButton) { + switch (e->key()) { + case Qt::Key_Right: + forward(); + return; + case Qt::Key_Left: + backward(); + return; + case Qt::Key_Up: + home(); + return; + } + } + Q3TextEdit::keyPressEvent(e); +} + +class QTextDetailPopup : public QWidget +{ +public: + QTextDetailPopup() + : QWidget (0, "automatic QText detail widget", Qt::WType_Popup) + { + setAttribute(Qt::WA_DeleteOnClose, true); + } + +protected: + void mousePressEvent(QMouseEvent *) + { + close(); + } +}; + + +void Q3TextBrowser::popupDetail(const QString& contents, const QPoint& pos) +{ + + const int shadowWidth = 6; // also used as '5' and '6' and even '8' below + const int vMargin = 8; + const int hMargin = 12; + + QWidget* popup = new QTextDetailPopup; + popup->setAttribute(Qt::WA_NoSystemBackground, true); + + Q3SimpleRichText* doc = new Q3SimpleRichText(contents, popup->font()); + doc->adjustSize(); + QRect r(0, 0, doc->width(), doc->height()); + + int w = r.width() + 2*hMargin; + int h = r.height() + 2*vMargin; + + popup->resize(w + shadowWidth, h + shadowWidth); + + // okay, now to find a suitable location + //###### we need a global fancy popup positioning somewhere + popup->move(pos - popup->rect().center()); + if (popup->geometry().right() > QApplication::desktop()->width()) + popup->move(QApplication::desktop()->width() - popup->width(), + popup->y()); + if (popup->geometry().bottom() > QApplication::desktop()->height()) + popup->move(popup->x(), + QApplication::desktop()->height() - popup->height()); + if (popup->x() < 0) + popup->move(0, popup->y()); + if (popup->y() < 0) + popup->move(popup->x(), 0); + + + popup->show(); + + // now for super-clever shadow stuff. super-clever mostly in + // how many window system problems it skirts around. + + QPainter p(popup); + p.setPen(QApplication::palette().color(QPalette::Active, QPalette::WindowText)); + p.drawRect(0, 0, w, h); + p.setPen(QApplication::palette().color(QPalette::Active, QPalette::Mid)); + p.setBrush(QColor(255, 255, 240)); + p.drawRect(1, 1, w-2, h-2); + p.setPen(Qt::black); + + doc->draw(&p, hMargin, vMargin, r, popup->palette(), 0); + delete doc; + + p.drawPoint(w + 5, 6); + p.drawLine(w + 3, 6, + w + 5, 8); + p.drawLine(w + 1, 6, + w + 5, 10); + int i; + for(i=7; i < h; i += 2) + p.drawLine(w, i, + w + 5, i + 5); + for(i = w - i + h; i > 6; i -= 2) + p.drawLine(i, h, + i + 5, h + 5); + for(; i > 0 ; i -= 2) + p.drawLine(6, h + 6 - i, + i + 5, h + 5); +} + +/*! + \fn void Q3TextBrowser::setText(const QString &txt) + + \overload + + Sets the text to \a txt. +*/ + +/*! + \reimp +*/ + +void Q3TextBrowser::setText(const QString &txt, const QString &context) +{ + d->textOrSourceChanged = true; + d->curmark = QLatin1String(""); + d->curmain = QLatin1String(""); + Q3TextEdit::setText(txt, context); +} + +void Q3TextBrowser::emitHighlighted(const QString &s) +{ + emit highlighted(s); +} + +void Q3TextBrowser::emitLinkClicked(const QString &s) +{ + d->textOrSourceChanged = false; + emit linkClicked(s); + if (!d->textOrSourceChanged) + setSource(s); +} + +QT_END_NAMESPACE + +#endif // QT_NO_TEXTBROWSER diff --git a/src/qt3support/text/q3textbrowser.h b/src/qt3support/text/q3textbrowser.h new file mode 100644 index 0000000..888685b --- /dev/null +++ b/src/qt3support/text/q3textbrowser.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3TEXTBROWSER_H +#define Q3TEXTBROWSER_H + +#include <QtGui/qpixmap.h> +#include <QtGui/qcolor.h> +#include <Qt3Support/q3textedit.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3SupportLight) + +#ifndef QT_NO_TEXTBROWSER + +class Q3TextBrowserData; + +class Q_COMPAT_EXPORT Q3TextBrowser : public Q3TextEdit +{ + Q_OBJECT + Q_PROPERTY(QString source READ source WRITE setSource) + + friend class Q3TextEdit; + +public: + Q3TextBrowser(QWidget* parent=0, const char* name=0); + ~Q3TextBrowser(); + + QString source() const; + +public Q_SLOTS: + virtual void setSource(const QString& name); + virtual void backward(); + virtual void forward(); + virtual void home(); + virtual void reload(); + void setText(const QString &txt) { setText(txt, QString()); } + virtual void setText(const QString &txt, const QString &context); + +Q_SIGNALS: + void backwardAvailable(bool); + void forwardAvailable(bool); + void sourceChanged(const QString&); + void highlighted(const QString&); + void linkClicked(const QString&); + void anchorClicked(const QString&, const QString&); + +protected: + void keyPressEvent(QKeyEvent * e); + +private: + Q_DISABLE_COPY(Q3TextBrowser) + + void popupDetail(const QString& contents, const QPoint& pos); + bool linksEnabled() const { return true; } + void emitHighlighted(const QString &s); + void emitLinkClicked(const QString &s); + Q3TextBrowserData *d; +}; + +#endif // QT_NO_TEXTBROWSER + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3TEXTBROWSER_H diff --git a/src/qt3support/text/q3textedit.cpp b/src/qt3support/text/q3textedit.cpp new file mode 100644 index 0000000..7577dce --- /dev/null +++ b/src/qt3support/text/q3textedit.cpp @@ -0,0 +1,7244 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3textedit.h" + +#ifndef QT_NO_TEXTEDIT + +#include <private/q3richtext_p.h> +#include "qpainter.h" +#include "qpen.h" +#include "qbrush.h" +#include "qpixmap.h" +#include "qfont.h" +#include "qcolor.h" +#include "qstyle.h" +#include "qsize.h" +#include "qevent.h" +#include "qtimer.h" +#include "qapplication.h" +#include "q3listbox.h" +#include "qclipboard.h" +#include "qcolordialog.h" +#include "q3stylesheet.h" +#include "q3dragobject.h" +#include "qurl.h" +#include "qcursor.h" +#include "qregexp.h" +#include "q3popupmenu.h" +#include "qstack.h" +#include "qmetaobject.h" +#include "q3textbrowser.h" +#include "private/q3syntaxhighlighter_p.h" +#include "qtextformat.h" +#ifndef QT_NO_IM +#include <qinputcontext.h> +#endif + +#ifndef QT_NO_ACCEL +#include <qkeysequence.h> +#define ACCEL_KEY(k) QLatin1Char('\t') + QString(QKeySequence(Qt::CTRL | Qt::Key_ ## k)) +#else +#define ACCEL_KEY(k) QLatin1Char('\t' )+ QString(QLatin1String("Ctrl+" #k)) +#endif + +#ifdef QT_TEXTEDIT_OPTIMIZATION +#define LOGOFFSET(i) d->logOffset + i +#endif + +QT_BEGIN_NAMESPACE + +struct QUndoRedoInfoPrivate +{ + Q3TextString text; +}; + +class Q3TextEditPrivate +{ +public: + Q3TextEditPrivate() + :preeditStart(-1),preeditLength(-1),numPreeditSelections(0),ensureCursorVisibleInShowEvent(false), + tabChangesFocus(false), +#ifndef QT_NO_CLIPBOARD + clipboard_mode(QClipboard::Clipboard), +#endif +#ifdef QT_TEXTEDIT_OPTIMIZATION + od(0), optimMode(false), + maxLogLines(-1), + logOffset(0), +#endif + autoFormatting((uint)Q3TextEdit::AutoAll), + cursorRepaintMode(false), + cursorBlinkActive(false) + + { + for (int i=0; i<7; i++) + id[i] = 0; + } + int id[7]; + int preeditStart; + int preeditLength; + int numPreeditSelections; + uint ensureCursorVisibleInShowEvent : 1; + uint tabChangesFocus : 1; + QString scrollToAnchor; // used to deferr scrollToAnchor() until the show event when we are resized + QString pressedName; + QString onName; +#ifndef QT_NO_CLIPBOARD + QClipboard::Mode clipboard_mode; +#endif + QTimer *trippleClickTimer; + QPoint trippleClickPoint; +#ifdef QT_TEXTEDIT_OPTIMIZATION + Q3TextEditOptimPrivate * od; + bool optimMode : 1; + int maxLogLines; + int logOffset; +#endif + Q3TextEdit::AutoFormatting autoFormatting; + uint cursorRepaintMode : 1; + uint cursorBlinkActive : 1; +}; + +#ifndef QT_NO_MIME +class Q3RichTextDrag : public Q3TextDrag +{ +public: + Q3RichTextDrag(QWidget *dragSource = 0, const char *name = 0); + + void setPlainText(const QString &txt) { setText(txt); } + void setRichText(const QString &txt) { richTxt = txt; } + + virtual QByteArray encodedData(const char *mime) const; + virtual const char* format(int i) const; + + static bool decode(QMimeSource *e, QString &str, const QString &mimetype, + const QString &subtype); + static bool canDecode(QMimeSource* e); + +private: + QString richTxt; + +}; + +Q3RichTextDrag::Q3RichTextDrag(QWidget *dragSource, const char *name) + : Q3TextDrag(dragSource, name) +{ +} + +QByteArray Q3RichTextDrag::encodedData(const char *mime) const +{ + if (qstrcmp("application/x-qrichtext", mime) == 0) { + return richTxt.toUtf8(); // #### perhaps we should use USC2 instead? + } else + return Q3TextDrag::encodedData(mime); +} + +bool Q3RichTextDrag::decode(QMimeSource *e, QString &str, const QString &mimetype, + const QString &subtype) +{ + if (mimetype == QLatin1String("application/x-qrichtext")) { + // do richtext decode + const char *mime; + int i; + for (i = 0; (mime = e->format(i)); ++i) { + if (qstrcmp("application/x-qrichtext", mime) != 0) + continue; + str = QString::fromUtf8(e->encodedData(mime)); + return true; + } + return false; + } + + // do a regular text decode + QString st = subtype; + return Q3TextDrag::decode(e, str, st); +} + +bool Q3RichTextDrag::canDecode(QMimeSource* e) +{ + if (e->provides("application/x-qrichtext")) + return true; + return Q3TextDrag::canDecode(e); +} + +const char* Q3RichTextDrag::format(int i) const +{ + if (Q3TextDrag::format(i)) + return Q3TextDrag::format(i); + if (Q3TextDrag::format(i-1)) + return "application/x-qrichtext"; + return 0; +} + +#endif + +static bool block_set_alignment = false; + +/*! + \class Q3TextEdit + \brief The Q3TextEdit widget provides a powerful single-page rich text editor. + + \compat + + \tableofcontents + + \section1 Introduction and Concepts + + Q3TextEdit is an advanced WYSIWYG viewer/editor supporting rich + text formatting using HTML-style tags. It is optimized to handle + large documents and to respond quickly to user input. + + Q3TextEdit has four modes of operation: + \table + \header \i Mode \i Command \i Notes + \row \i Plain Text Editor \i setTextFormat(Qt::PlainText) + \i Set text with setText(); text() returns plain text. Text + attributes (e.g. colors) can be set, but plain text is always + returned. + \row \i Rich Text Editor \i setTextFormat(Qt::RichText) + \i Set text with setText(); text() returns rich text. Rich + text editing is fairly limited. You can't set margins or + insert images for example (although you can read and + correctly display files that have margins set and that + include images). This mode is mostly useful for editing small + amounts of rich text. + \row \i Text Viewer \i setReadOnly(true) + \i Set text with setText() or append() (which has no undo + history so is faster and uses less memory); text() returns + plain or rich text depending on the textFormat(). This mode + can correctly display a large subset of HTML tags. + \row \i Log Viewer \i setTextFormat(Qt::LogText) + \i Append text using append(). The widget is set to be read + only and rich text support is disabled although a few HTML + tags (for color, bold, italic and underline) may be used. + (See \link #logtextmode Qt::LogText mode\endlink for details.) + \endtable + + Q3TextEdit can be used as a syntax highlighting editor when used in + conjunction with QSyntaxHighlighter. + + We recommend that you always call setTextFormat() to set the mode + you want to use. If you use Qt::AutoText then setText() and + append() will try to determine whether the text they are given is + plain text or rich text. If you use Qt::RichText then setText() and + append() will assume that the text they are given is rich text. + insert() simply inserts the text it is given. + + Q3TextEdit works on paragraphs and characters. A paragraph is a + formatted string which is word-wrapped to fit into the width of + the widget. By default when reading plain text, one newline + signify a paragraph. A document consists of zero or more + paragraphs, indexed from 0. Characters are indexed on a + per-paragraph basis, also indexed from 0. The words in the + paragraph are aligned in accordance with the paragraph's + alignment(). Paragraphs are separated by hard line breaks. Each + character within a paragraph has its own attributes, for example, + font and color. + + The text edit documentation uses the following concepts: + \list + \i \e{current format} -- + this is the format at the current cursor position, \e and it + is the format of the selected text if any. + \i \e{current paragraph} -- the paragraph which contains the + cursor. + \endlist + + Q3TextEdit can display images (using Q3MimeSourceFactory), lists and + tables. If the text is too large to view within the text edit's + viewport, scroll bars will appear. The text edit can load both + plain text and HTML files (a subset of HTML 3.2 and 4). The + rendering style and the set of valid tags are defined by a + styleSheet(). Custom tags can be created and placed in a custom + style sheet. Change the style sheet with \l{setStyleSheet()}; see + Q3StyleSheet for details. The images identified by image tags are + displayed if they can be interpreted using the text edit's + \l{Q3MimeSourceFactory}; see setMimeSourceFactory(). + + If you want a text browser with more navigation use QTextBrowser. + If you just need to display a small piece of rich text use QLabel + or QSimpleRichText. + + If you create a new Q3TextEdit, and want to allow the user to edit + rich text, call setTextFormat(Qt::RichText) to ensure that the + text is treated as rich text. (Rich text uses HTML tags to set + text formatting attributes. See Q3StyleSheet for information on the + HTML tags that are supported.). If you don't call setTextFormat() + explicitly the text edit will guess from the text itself whether + it is rich text or plain text. This means that if the text looks + like HTML or XML it will probably be interpreted as rich text, so + you should call setTextFormat(Qt::PlainText) to preserve such + text. + + Note that we do not intend to add a full-featured web browser + widget to Qt (because that would easily double Qt's size and only + a few applications would benefit from it). The rich + text support in Qt is designed to provide a fast, portable and + efficient way to add reasonable online help facilities to + applications, and to provide a basis for rich text editors. + + \section1 Using Q3TextEdit as a Display Widget + + Q3TextEdit can display a large HTML subset, including tables and + images. + + The text is set or replaced using setText() which deletes any + existing text and replaces it with the text passed in the + setText() call. If you call setText() with legacy HTML (with + setTextFormat(Qt::RichText) in force), and then call text(), the text + that is returned may have different markup, but will render the + same. Text can be inserted with insert(), paste(), pasteSubType() + and append(). Text that is appended does not go into the undo + history; this makes append() faster and consumes less memory. Text + can also be cut(). The entire text is deleted with clear() and the + selected text is deleted with removeSelectedText(). Selected + (marked) text can also be deleted with del() (which will delete + the character to the right of the cursor if no text is selected). + + Loading and saving text is achieved using setText() and text(), + for example: + \snippet doc/src/snippets/code/src_qt3support_text_q3textedit.cpp 0 + + By default the text edit wraps words at whitespace to fit within + the text edit widget. The setWordWrap() function is used to + specify the kind of word wrap you want, or \c NoWrap if you don't + want any wrapping. Call setWordWrap() to set a fixed pixel width + \c FixedPixelWidth, or character column (e.g. 80 column) \c + FixedColumnWidth with the pixels or columns specified with + setWrapColumnOrWidth(). If you use word wrap to the widget's width + \c WidgetWidth, you can specify whether to break on whitespace or + anywhere with setWrapPolicy(). + + The background color is set differently than other widgets, using + setPaper(). You specify a brush style which could be a plain color + or a complex pixmap. + + Hypertext links are automatically underlined; this can be changed + with setLinkUnderline(). The tab stop width is set with + setTabStopWidth(). + + The zoomIn() and zoomOut() functions can be used to resize the + text by increasing (decreasing for zoomOut()) the point size used. + Images are not affected by the zoom functions. + + The lines() function returns the number of lines in the text and + paragraphs() returns the number of paragraphs. The number of lines + within a particular paragraph is returned by linesOfParagraph(). + The length of the entire text in characters is returned by + length(). + + You can scroll to an anchor in the text, e.g. + \c{<a name="anchor">} with scrollToAnchor(). The find() function + can be used to find and select a given string within the text. + + A read-only Q3TextEdit provides the same functionality as the + (obsolete) QTextView. (QTextView is still supplied for + compatibility with old code.) + + \section2 Read-only key bindings + + When Q3TextEdit is used read-only the key-bindings are limited to + navigation, and text may only be selected with the mouse: + \table + \header \i Keypresses \i Action + \row \i Up \i Move one line up + \row \i Down \i Move one line down + \row \i Left \i Move one character left + \row \i Right \i Move one character right + \row \i PageUp \i Move one (viewport) page up + \row \i PageDown \i Move one (viewport) page down + \row \i Home \i Move to the beginning of the text + \row \i End \i Move to the end of the text + \row \i Shift+Wheel + \i Scroll the page horizontally (the Wheel is the mouse wheel) + \row \i Ctrl+Wheel \i Zoom the text + \endtable + + The text edit may be able to provide some meta-information. For + example, the documentTitle() function will return the text from + within HTML \c{<title>} tags. + + The text displayed in a text edit has a \e context. The context is + a path which the text edit's Q3MimeSourceFactory uses to resolve + the locations of files and images. It is passed to the + mimeSourceFactory() when quering data. (See Q3TextEdit() and + \l{context()}.) + + \target logtextmode + \section2 Using Q3TextEdit in Qt::LogText Mode + + Setting the text format to Qt::LogText puts the widget in a special + mode which is optimized for very large texts. In this mode editing + and rich text support are disabled (the widget is explicitly set + to read-only mode). This allows the text to be stored in a + different, more memory efficient manner. However, a certain degree + of text formatting is supported through the use of formatting + tags. A tag is delimited by \c < and \c {>}. The characters \c + {<}, \c > and \c & are escaped by using \c {<}, \c {>} and + \c {&}. A tag pair consists of a left and a right tag (or + open/close tags). Left-tags mark the starting point for + formatting, while right-tags mark the ending point. A right-tag + always start with a \c / before the tag keyword. For example \c + <b> and \c </b> are a tag pair. Tags can be nested, but they + have to be closed in the same order as they are opened. For + example, \c <b><u></u></b> is valid, while \c + <b><u></b></u> will output an error message. + + By using tags it is possible to change the color, bold, italic and + underline settings for a piece of text. A color can be specified + by using the HTML font tag \c {<font color=colorname>}. The color + name can be one of the color names from the X11 color database, or + a RGB hex value (e.g \c {#00ff00}). Example of valid color tags: + \c {<font color=red>}, \c{<font color="light blue">},\c {<font + color="#223344">}. Bold, italic and underline settings can be + specified by the tags \c {<b>}, \c <i> and \c {<u>}. Note that a + tag does not necessarily have to be closed. A valid example: + \snippet doc/src/snippets/code/src_qt3support_text_q3textedit.cpp 1 + + Stylesheets can also be used in Qt::LogText mode. To create and use a + custom tag, you could do the following: + \snippet doc/src/snippets/code/src_qt3support_text_q3textedit.cpp 2 + Note that only the color, bold, underline and italic attributes of + a Q3StyleSheetItem is used in Qt::LogText mode. + + Note that you can use setMaxLogLines() to limit the number of + lines the widget can hold in Qt::LogText mode. + + There are a few things that you need to be aware of when the + widget is in this mode: + \list + \i Functions that deal with rich text formatting and cursor + movement will not work or return anything valid. + \i Lines are equivalent to paragraphs. + \endlist + + \section1 Using Q3TextEdit as an Editor + + All the information about using Q3TextEdit as a display widget also + applies here. + + The current format's attributes are set with setItalic(), + setBold(), setUnderline(), setFamily() (font family), + setPointSize(), setColor() and setCurrentFont(). The current + paragraph's alignment is set with setAlignment(). + + Use setSelection() to select text. The setSelectionAttributes() + function is used to indicate how selected text should be + displayed. Use hasSelectedText() to find out if any text is + selected. The currently selected text's position is available + using getSelection() and the selected text itself is returned by + selectedText(). The selection can be copied to the clipboard with + copy(), or cut to the clipboard with cut(). It can be deleted with + removeSelectedText(). The entire text can be selected (or + unselected) using selectAll(). Q3TextEdit supports multiple + selections. Most of the selection functions operate on the default + selection, selection 0. If the user presses a non-selecting key, + e.g. a cursor key without also holding down Shift, all selections + are cleared. + + Set and get the position of the cursor with setCursorPosition() + and getCursorPosition() respectively. When the cursor is moved, + the signals currentFontChanged(), currentColorChanged() and + currentAlignmentChanged() are emitted to reflect the font, color + and alignment at the new cursor position. + + If the text changes, the textChanged() signal is emitted, and if + the user inserts a new line by pressing Return or Enter, + returnPressed() is emitted. The isModified() function will return + true if the text has been modified. + + Q3TextEdit provides command-based undo and redo. To set the depth + of the command history use setUndoDepth() which defaults to 100 + steps. To undo or redo the last operation call undo() or redo(). + The signals undoAvailable() and redoAvailable() indicate whether + the undo and redo operations can be executed. + + \section2 Editing key bindings + + The list of key-bindings which are implemented for editing: + \table + \header \i Keypresses \i Action + \row \i Backspace \i Delete the character to the left of the cursor + \row \i Delete \i Delete the character to the right of the cursor + \row \i Ctrl+A \i Move the cursor to the beginning of the line + \row \i Ctrl+B \i Move the cursor one character left + \row \i Ctrl+C \i Copy the marked text to the clipboard (also + Ctrl+Insert under Windows) + \row \i Ctrl+D \i Delete the character to the right of the cursor + \row \i Ctrl+E \i Move the cursor to the end of the line + \row \i Ctrl+F \i Move the cursor one character right + \row \i Ctrl+H \i Delete the character to the left of the cursor + \row \i Ctrl+K \i Delete to end of line + \row \i Ctrl+N \i Move the cursor one line down + \row \i Ctrl+P \i Move the cursor one line up + \row \i Ctrl+V \i Paste the clipboard text into line edit + (also Shift+Insert under Windows) + \row \i Ctrl+X \i Cut the marked text, copy to clipboard + (also Shift+Delete under Windows) + \row \i Ctrl+Z \i Undo the last operation + \row \i Ctrl+Y \i Redo the last operation + \row \i Left \i Move the cursor one character left + \row \i Ctrl+Left \i Move the cursor one word left + \row \i Right \i Move the cursor one character right + \row \i Ctrl+Right \i Move the cursor one word right + \row \i Up \i Move the cursor one line up + \row \i Ctrl+Qt::Up \i Move the cursor one word up + \row \i DownArrow \i Move the cursor one line down + \row \i Ctrl+Down \i Move the cursor one word down + \row \i PageUp \i Move the cursor one page up + \row \i PageDown \i Move the cursor one page down + \row \i Home \i Move the cursor to the beginning of the line + \row \i Ctrl+Home \i Move the cursor to the beginning of the text + \row \i End \i Move the cursor to the end of the line + \row \i Ctrl+End \i Move the cursor to the end of the text + \row \i Shift+Wheel \i Scroll the page horizontally + (the Wheel is the mouse wheel) + \row \i Ctrl+Wheel \i Zoom the text + \endtable + + To select (mark) text hold down the Shift key whilst pressing one + of the movement keystrokes, for example, \e{Shift+Right} + will select the character to the right, and \e{Shift+Ctrl+Right} will select the word to the right, etc. + + By default the text edit widget operates in insert mode so all + text that the user enters is inserted into the text edit and any + text to the right of the cursor is moved out of the way. The mode + can be changed to overwrite, where new text overwrites any text to + the right of the cursor, using setOverwriteMode(). +*/ + +/*! + \enum Q3TextEdit::AutoFormattingFlag + + \value AutoNone Do not perform any automatic formatting + \value AutoBulletList Only automatically format bulletted lists + \value AutoAll Apply all available autoformatting +*/ + + +/*! + \enum Q3TextEdit::KeyboardAction + + This enum is used by doKeyboardAction() to specify which action + should be executed: + + \value ActionBackspace Delete the character to the left of the + cursor. + + \value ActionDelete Delete the character to the right of the + cursor. + + \value ActionReturn Split the paragraph at the cursor position. + + \value ActionKill If the cursor is not at the end of the + paragraph, delete the text from the cursor position until the end + of the paragraph. If the cursor is at the end of the paragraph, + delete the hard line break at the end of the paragraph: this will + cause this paragraph to be joined with the following paragraph. + + \value ActionWordBackspace Delete the word to the left of the + cursor position. + + \value ActionWordDelete Delete the word to the right of the + cursor position + +*/ + +/*! + \enum Q3TextEdit::VerticalAlignment + + This enum is used to set the vertical alignment of the text. + + \value AlignNormal Normal alignment + \value AlignSuperScript Superscript + \value AlignSubScript Subscript +*/ + +/*! + \enum Q3TextEdit::TextInsertionFlags + + \internal + + \value RedoIndentation + \value CheckNewLines + \value RemoveSelected +*/ + + +/*! + \fn void Q3TextEdit::copyAvailable(bool yes) + + This signal is emitted when text is selected or de-selected in the + text edit. + + When text is selected this signal will be emitted with \a yes set + to true. If no text has been selected or if the selected text is + de-selected this signal is emitted with \a yes set to false. + + If \a yes is true then copy() can be used to copy the selection to + the clipboard. If \a yes is false then copy() does nothing. + + \sa selectionChanged() +*/ + + +/*! + \fn void Q3TextEdit::textChanged() + + This signal is emitted whenever the text in the text edit changes. + + \sa setText() append() +*/ + +/*! + \fn void Q3TextEdit::selectionChanged() + + This signal is emitted whenever the selection changes. + + \sa setSelection() copyAvailable() +*/ + +/*! \fn Q3TextDocument *Q3TextEdit::document() const + + \internal + + This function returns the Q3TextDocument which is used by the text + edit. +*/ + +/*! \fn void Q3TextEdit::setDocument(Q3TextDocument *doc) + + \internal + + This function sets the Q3TextDocument which should be used by the text + edit to \a doc. This can be used, for example, if you want to + display a document using multiple views. You would create a + Q3TextDocument and set it to the text edits which should display it. + You would need to connect to the textChanged() and + selectionChanged() signals of all the text edits and update them all + accordingly (preferably with a slight delay for efficiency reasons). +*/ + +/*! + \enum Q3TextEdit::CursorAction + + This enum is used by moveCursor() to specify in which direction + the cursor should be moved: + + \value MoveBackward Moves the cursor one character backward + + \value MoveWordBackward Moves the cursor one word backward + + \value MoveForward Moves the cursor one character forward + + \value MoveWordForward Moves the cursor one word forward + + \value MoveUp Moves the cursor up one line + + \value MoveDown Moves the cursor down one line + + \value MoveLineStart Moves the cursor to the beginning of the line + + \value MoveLineEnd Moves the cursor to the end of the line + + \value MoveHome Moves the cursor to the beginning of the document + + \value MoveEnd Moves the cursor to the end of the document + + \value MovePgUp Moves the cursor one viewport page up + + \value MovePgDown Moves the cursor one viewport page down +*/ + +/*! + \property Q3TextEdit::overwriteMode + \brief the text edit's overwrite mode + + If false (the default) characters entered by the user are inserted + with any characters to the right being moved out of the way. If + true, the editor is in overwrite mode, i.e. characters entered by + the user overwrite any characters to the right of the cursor + position. +*/ + +/*! + \fn void Q3TextEdit::setCurrentFont(const QFont &f) + + Sets the font of the current format to \a f. + + If the widget is in Qt::LogText mode this function will do + nothing. Use setFont() instead. + + \sa currentFont() setPointSize() setFamily() +*/ + +/*! + \property Q3TextEdit::undoDepth + \brief the depth of the undo history + + The maximum number of steps in the undo/redo history. The default + is 100. + + \sa undo() redo() +*/ + +/*! + \fn void Q3TextEdit::undoAvailable(bool yes) + + This signal is emitted when the availability of undo changes. If + \a yes is true, then undo() will work until undoAvailable(false) + is next emitted. + + \sa undo() undoDepth() +*/ + +/*! + \fn void Q3TextEdit::modificationChanged(bool m) + + This signal is emitted when the modification status of the + document has changed. If \a m is true, the document was modified, + otherwise the modification state has been reset to unmodified. + + \sa modified +*/ + +/*! + \fn void Q3TextEdit::redoAvailable(bool yes) + + This signal is emitted when the availability of redo changes. If + \a yes is true, then redo() will work until redoAvailable(false) + is next emitted. + + \sa redo() undoDepth() +*/ + +/*! + \fn void Q3TextEdit::currentFontChanged(const QFont &f) + + This signal is emitted if the font of the current format has + changed. + + The new font is \a f. + + \sa setCurrentFont() +*/ + +/*! + \fn void Q3TextEdit::currentColorChanged(const QColor &c) + + This signal is emitted if the color of the current format has + changed. + + The new color is \a c. + + \sa setColor() +*/ + +/*! + \fn void Q3TextEdit::currentVerticalAlignmentChanged(Q3TextEdit::VerticalAlignment a) + + This signal is emitted if the vertical alignment of the current + format has changed. + + The new vertical alignment is \a a. +*/ + +/*! + \fn void Q3TextEdit::currentAlignmentChanged(int a) + + This signal is emitted if the alignment of the current paragraph + has changed. + + The new alignment is \a a. + + \sa setAlignment() +*/ + +/*! + \fn void Q3TextEdit::cursorPositionChanged(Q3TextCursor *c) + + \internal +*/ + +/*! + \fn void Q3TextEdit::cursorPositionChanged(int para, int pos) + + \overload + + This signal is emitted if the position of the cursor has changed. + \a para contains the paragraph index and \a pos contains the + character position within the paragraph. + + \sa setCursorPosition() +*/ + +/*! + \fn void Q3TextEdit::clicked(int para, int pos) + + This signal is emitted when the mouse is clicked on the paragraph + \a para at character position \a pos. + + \sa doubleClicked() +*/ + +/*! \fn void Q3TextEdit::doubleClicked(int para, int pos) + + This signal is emitted when the mouse is double-clicked on the + paragraph \a para at character position \a pos. + + \sa clicked() +*/ + + +/*! + \fn void Q3TextEdit::returnPressed() + + This signal is emitted if the user pressed the Return or the Enter + key. +*/ + +/*! + \fn Q3TextCursor *Q3TextEdit::textCursor() const + + Returns the text edit's text cursor. + + \warning Q3TextCursor is not in the public API, but in special + circumstances you might wish to use it. +*/ + +/*! + Constructs an empty Q3TextEdit called \a name, with parent \a + parent. +*/ + +Q3TextEdit::Q3TextEdit(QWidget *parent, const char *name) + : Q3ScrollView(parent, name, Qt::WStaticContents | Qt::WNoAutoErase), + doc(new Q3TextDocument(0)), undoRedoInfo(doc) +{ + init(); +} + +/*! + Constructs a Q3TextEdit called \a name, with parent \a parent. The + text edit will display the text \a text using context \a context. + + The \a context is a path which the text edit's Q3MimeSourceFactory + uses to resolve the locations of files and images. It is passed to + the mimeSourceFactory() when quering data. + + For example if the text contains an image tag, + \c{<img src="image.png">}, and the context is "path/to/look/in", the + Q3MimeSourceFactory will try to load the image from + "path/to/look/in/image.png". If the tag was + \c{<img src="/image.png">}, the context will not be used (because + Q3MimeSourceFactory recognizes that we have used an absolute path) + and will try to load "/image.png". The context is applied in exactly + the same way to \e hrefs, for example, + \c{<a href="target.html">Target</a>}, would resolve to + "path/to/look/in/target.html". +*/ + +Q3TextEdit::Q3TextEdit(const QString& text, const QString& context, + QWidget *parent, const char *name) + : Q3ScrollView(parent, name, Qt::WStaticContents | Qt::WNoAutoErase), + doc(new Q3TextDocument(0)), undoRedoInfo(doc) +{ + init(); + setText(text, context); +} + +/*! + Destructor. +*/ + +Q3TextEdit::~Q3TextEdit() +{ + delete undoRedoInfo.d; + undoRedoInfo.d = 0; + delete cursor; + delete doc; +#ifdef QT_TEXTEDIT_OPTIMIZATION + delete d->od; +#endif + delete d; +} + +void Q3TextEdit::init() +{ + d = new Q3TextEditPrivate; + doc->formatCollection()->setPaintDevice(this); + undoEnabled = true; + readonly = true; + setReadOnly(false); + setFrameStyle(LineEditPanel | Sunken); + connect(doc, SIGNAL(minimumWidthChanged(int)), + this, SLOT(documentWidthChanged(int))); + + mousePressed = false; + inDoubleClick = false; + modified = false; + mightStartDrag = false; + onLink.clear(); + d->onName.clear(); + overWrite = false; + wrapMode = WidgetWidth; + wrapWidth = -1; + wPolicy = AtWhiteSpace; + inDnD = false; + doc->setFormatter(new Q3TextFormatterBreakWords); + QFont f = Q3ScrollView::font(); + if (f.kerning()) + f.setKerning(false); + doc->formatCollection()->defaultFormat()->setFont(f); + doc->formatCollection()->defaultFormat()->setColor(palette().color(QPalette::Text)); + currentFormat = doc->formatCollection()->defaultFormat(); + currentAlignment = Qt::AlignAuto; + + setBackgroundRole(QPalette::Base); + viewport()->setBackgroundRole(QPalette::Base); + + viewport()->setAcceptDrops(true); + resizeContents(0, doc->lastParagraph() ? + (doc->lastParagraph()->paragId() + 1) * doc->formatCollection()->defaultFormat()->height() : 0); + + setAttribute(Qt::WA_KeyCompression, true); + viewport()->setMouseTracking(true); +#ifndef QT_NO_CURSOR + viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor); +#endif + cursor = new Q3TextCursor(doc); + + formatTimer = new QTimer(this); + connect(formatTimer, SIGNAL(timeout()), + this, SLOT(formatMore())); + lastFormatted = doc->firstParagraph(); + + scrollTimer = new QTimer(this); + connect(scrollTimer, SIGNAL(timeout()), + this, SLOT(autoScrollTimerDone())); + + interval = 0; + changeIntervalTimer = new QTimer(this); + connect(changeIntervalTimer, SIGNAL(timeout()), + this, SLOT(doChangeInterval())); + + cursorVisible = true; + blinkTimer = new QTimer(this); + connect(blinkTimer, SIGNAL(timeout()), + this, SLOT(blinkCursor())); + +#ifndef QT_NO_DRAGANDDROP + dragStartTimer = new QTimer(this); + connect(dragStartTimer, SIGNAL(timeout()), + this, SLOT(startDrag())); +#endif + + d->trippleClickTimer = new QTimer(this); + + formatMore(); + + blinkCursorVisible = false; + + viewport()->setFocusProxy(this); + viewport()->setFocusPolicy(Qt::WheelFocus); + setFocusPolicy(Qt::WheelFocus); + setInputMethodEnabled(true); + viewport()->installEventFilter(this); + connect(this, SIGNAL(horizontalSliderReleased()), this, SLOT(sliderReleased())); + connect(this, SIGNAL(verticalSliderReleased()), this, SLOT(sliderReleased())); + installEventFilter(this); +} + +void Q3TextEdit::paintDocument(bool drawAll, QPainter *p, int cx, int cy, int cw, int ch) +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + Q_ASSERT(!d->optimMode); + if (d->optimMode) + return; +#endif + + bool drawCur = blinkCursorVisible && (hasFocus() || viewport()->hasFocus()); + if ((hasSelectedText() && !style()->styleHint(QStyle::SH_BlinkCursorWhenTextSelected, 0, this)) || + isReadOnly() || !cursorVisible) + drawCur = false; + QPalette pal = palette(); + if (doc->paper()) + pal.setBrush(QPalette::Base, *doc->paper()); + + if (contentsY() < doc->y()) { + p->fillRect(contentsX(), contentsY(), visibleWidth(), doc->y(), + pal.base()); + } + if (drawAll && doc->width() - contentsX() < cx + cw) { + p->fillRect(doc->width() - contentsX(), cy, cx + cw - doc->width() + contentsX(), ch, + pal.base()); + } + + p->setBrushOrigin(-contentsX(), -contentsY()); + + lastFormatted = doc->draw(p, cx, cy, cw, ch, pal, !drawAll, drawCur, cursor); + + if (lastFormatted == doc->lastParagraph()) + resizeContents(contentsWidth(), doc->height()); + + if (contentsHeight() < visibleHeight() && (!doc->lastParagraph() || doc->lastParagraph()->isValid()) && drawAll) + p->fillRect(0, contentsHeight(), visibleWidth(), + visibleHeight() - contentsHeight(), pal.base()); +} + +/*! + \reimp +*/ + +void Q3TextEdit::drawContents(QPainter *p, int cx, int cy, int cw, int ch) +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + optimDrawContents(p, cx, cy, cw, ch); + return; + } +#endif + paintDocument(true, p, cx, cy, cw, ch); + int v; + p->setPen(palette().color(foregroundRole())); + if (document()->isPageBreakEnabled() && (v = document()->flow()->pageSize()) > 0) { + int l = int(cy / v) * v; + while (l < cy + ch) { + p->drawLine(cx, l, cx + cw - 1, l); + l += v; + } + } +} + +/*! + \internal +*/ + +void Q3TextEdit::drawContents(QPainter *p) +{ + if (horizontalScrollBar()->isVisible() && + verticalScrollBar()->isVisible()) { + const QRect verticalRect = verticalScrollBar()->geometry(); + const QRect horizontalRect = horizontalScrollBar()->geometry(); + + QRect cornerRect; + cornerRect.setTop(verticalRect.bottom()); + cornerRect.setBottom(horizontalRect.bottom()); + cornerRect.setLeft(verticalRect.left()); + cornerRect.setRight(verticalRect.right()); + + p->fillRect(cornerRect, palette().background()); + } +} + +/*! + \reimp +*/ + +bool Q3TextEdit::event(QEvent *e) +{ + if (e->type() == QEvent::AccelOverride && !isReadOnly()) { + QKeyEvent* ke = (QKeyEvent*) e; + switch(ke->state()) { + case Qt::NoButton: + case Qt::Keypad: + case Qt::ShiftButton: + if (ke->key() < Qt::Key_Escape) { + ke->accept(); + } else { + switch (ke->key()) { + case Qt::Key_Return: + case Qt::Key_Enter: + case Qt::Key_Delete: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_Backspace: + case Qt::Key_Left: + case Qt::Key_Right: + ke->accept(); + default: + break; + } + } + break; + + case Qt::ControlButton: + case Qt::ControlButton|Qt::ShiftButton: + case Qt::ControlButton|Qt::Keypad: + case Qt::ControlButton|Qt::ShiftButton|Qt::Keypad: + switch (ke->key()) { + case Qt::Key_Tab: + case Qt::Key_Backtab: + ke->ignore(); + break; +// Those are too frequently used for application functionality +/* case Qt::Key_A: + case Qt::Key_B: + case Qt::Key_D: + case Qt::Key_E: + case Qt::Key_F: + case Qt::Key_H: + case Qt::Key_I: + case Qt::Key_K: + case Qt::Key_N: + case Qt::Key_P: + case Qt::Key_T: +*/ + case Qt::Key_C: + case Qt::Key_V: + case Qt::Key_X: + case Qt::Key_Y: + case Qt::Key_Z: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Home: + case Qt::Key_End: +#if defined (Q_WS_WIN) + case Qt::Key_Insert: + case Qt::Key_Delete: +#endif + ke->accept(); + default: + break; + } + break; + + default: + switch (ke->key()) { +#if defined (Q_WS_WIN) + case Qt::Key_Insert: + ke->accept(); +#endif + default: + break; + } + break; + } + } + + if (e->type() == QEvent::Show) { + if ( +#ifdef QT_TEXTEDIT_OPTIMIZATION + !d->optimMode && +#endif + d->ensureCursorVisibleInShowEvent ) { + ensureCursorVisible(); + d->ensureCursorVisibleInShowEvent = false; + } + if (!d->scrollToAnchor.isEmpty()) { + scrollToAnchor(d->scrollToAnchor); + d->scrollToAnchor.clear(); + } + } + return QWidget::event(e); +} + +/*! + Processes the key event, \a e. By default key events are used to + provide keyboard navigation and text editing. +*/ + +void Q3TextEdit::keyPressEvent(QKeyEvent *e) +{ + changeIntervalTimer->stop(); + interval = 10; + bool unknownKey = false; + if (isReadOnly()) { + if (!handleReadOnlyKeyEvent(e)) + Q3ScrollView::keyPressEvent(e); + changeIntervalTimer->start(100, true); + return; + } + + + bool selChanged = false; + for (int i = 1; i < doc->numSelections(); ++i) // start with 1 as we don't want to remove the Standard-Selection + selChanged = doc->removeSelection(i) || selChanged; + + if (selChanged) { + cursor->paragraph()->document()->nextDoubleBuffered = true; + repaintChanged(); + } + + bool clearUndoRedoInfo = true; + + + switch (e->key()) { + case Qt::Key_Left: + case Qt::Key_Right: { + // a bit hacky, but can't change this without introducing new enum values for move and keeping the + // correct semantics and movement for BiDi and non BiDi text. + CursorAction a; + if (cursor->paragraph()->string()->isRightToLeft() == (e->key() == Qt::Key_Right)) + a = e->state() & Qt::ControlButton ? MoveWordBackward : MoveBackward; + else + a = e->state() & Qt::ControlButton ? MoveWordForward : MoveForward; + moveCursor(a, e->state() & Qt::ShiftButton); + break; + } + case Qt::Key_Up: + moveCursor(e->state() & Qt::ControlButton ? MovePgUp : MoveUp, e->state() & Qt::ShiftButton); + break; + case Qt::Key_Down: + moveCursor(e->state() & Qt::ControlButton ? MovePgDown : MoveDown, e->state() & Qt::ShiftButton); + break; + case Qt::Key_Home: + moveCursor(e->state() & Qt::ControlButton ? MoveHome : MoveLineStart, e->state() & Qt::ShiftButton); + break; + case Qt::Key_End: + moveCursor(e->state() & Qt::ControlButton ? MoveEnd : MoveLineEnd, e->state() & Qt::ShiftButton); + break; + case Qt::Key_Prior: + moveCursor(MovePgUp, e->state() & Qt::ShiftButton); + break; + case Qt::Key_Next: + moveCursor(MovePgDown, e->state() & Qt::ShiftButton); + break; + case Qt::Key_Return: case Qt::Key_Enter: + if (doc->hasSelection(Q3TextDocument::Standard, false)) + removeSelectedText(); + if (textFormat() == Qt::RichText && (e->state() & Qt::ControlButton)) { + // Ctrl-Enter inserts a line break in rich text mode + insert(QString(QChar(QChar::LineSeparator)), true, false); + } else { +#ifndef QT_NO_CURSOR + viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor); +#endif + clearUndoRedoInfo = false; + doKeyboardAction(ActionReturn); + emit returnPressed(); + } + break; + case Qt::Key_Delete: +#if defined (Q_WS_WIN) + if (e->state() & Qt::ShiftButton) { + cut(); + break; + } else +#endif + if (doc->hasSelection(Q3TextDocument::Standard, true)) { + removeSelectedText(); + break; + } + doKeyboardAction(e->state() & Qt::ControlButton ? ActionWordDelete + : ActionDelete); + clearUndoRedoInfo = false; + + break; + case Qt::Key_Insert: + if (e->state() & Qt::ShiftButton) + paste(); +#if defined (Q_WS_WIN) + else if (e->state() & Qt::ControlButton) + copy(); +#endif + else + setOverwriteMode(!isOverwriteMode()); + break; + case Qt::Key_Backspace: +#if defined (Q_WS_WIN) + if (e->state() & Qt::AltButton) { + if (e->state() & Qt::ControlButton) { + break; + } else if (e->state() & Qt::ShiftButton) { + redo(); + break; + } else { + undo(); + break; + } + } else +#endif + if (doc->hasSelection(Q3TextDocument::Standard, true)) { + removeSelectedText(); + break; + } + + doKeyboardAction(e->state() & Qt::ControlButton ? ActionWordBackspace + : ActionBackspace); + clearUndoRedoInfo = false; + break; + case Qt::Key_F16: // Copy key on Sun keyboards + copy(); + break; + case Qt::Key_F18: // Paste key on Sun keyboards + paste(); + break; + case Qt::Key_F20: // Cut key on Sun keyboards + cut(); + break; + case Qt::Key_Direction_L: + if (doc->textFormat() == Qt::PlainText) { + // change the whole doc + Q3TextParagraph *p = doc->firstParagraph(); + while (p) { + p->setDirection(QChar::DirL); + p->setAlignment(Qt::AlignLeft); + p->invalidate(0); + p = p->next(); + } + } else { + if (!cursor->paragraph() || cursor->paragraph()->direction() == QChar::DirL) + return; + cursor->paragraph()->setDirection(QChar::DirL); + if (cursor->paragraph()->length() <= 1&& + ((cursor->paragraph()->alignment() & (Qt::AlignLeft | Qt::AlignRight)) != 0)) + setAlignment(Qt::AlignLeft); + } + repaintChanged(); + break; + case Qt::Key_Direction_R: + if (doc->textFormat() == Qt::PlainText) { + // change the whole doc + Q3TextParagraph *p = doc->firstParagraph(); + while (p) { + p->setDirection(QChar::DirR); + p->setAlignment(Qt::AlignRight); + p->invalidate(0); + p = p->next(); + } + } else { + if (!cursor->paragraph() || cursor->paragraph()->direction() == QChar::DirR) + return; + cursor->paragraph()->setDirection(QChar::DirR); + if (cursor->paragraph()->length() <= 1&& + ((cursor->paragraph()->alignment() & (Qt::AlignLeft | Qt::AlignRight)) != 0)) + setAlignment(Qt::AlignRight); + } + repaintChanged(); + break; + default: { + unsigned char ascii = e->text().length() ? e->text().unicode()->latin1() : 0; + if (e->text().length() && + ((!(e->state() & Qt::ControlButton) && +#ifndef Q_OS_MAC + !(e->state() & Qt::AltButton) && +#endif + !(e->state() & Qt::MetaButton)) || + (((e->state() & (Qt::ControlButton | Qt::AltButton))) == (Qt::ControlButton|Qt::AltButton))) && + (!ascii || ascii >= 32 || e->text() == QString(QLatin1Char('\t')))) { + clearUndoRedoInfo = false; + if (e->key() == Qt::Key_Tab) { + if (d->tabChangesFocus) { + e->ignore(); + break; + } + if (textFormat() == Qt::RichText && cursor->index() == 0 + && (cursor->paragraph()->isListItem() || cursor->paragraph()->listDepth())) { + clearUndoRedo(); + undoRedoInfo.type = UndoRedoInfo::Style; + undoRedoInfo.id = cursor->paragraph()->paragId(); + undoRedoInfo.eid = undoRedoInfo.id; + undoRedoInfo.styleInformation = Q3TextStyleCommand::readStyleInformation(doc, undoRedoInfo.id, undoRedoInfo.eid); + cursor->paragraph()->setListDepth(cursor->paragraph()->listDepth() +1); + clearUndoRedo(); + drawCursor(false); + repaintChanged(); + drawCursor(true); + break; + } + } else if (e->key() == Qt::Key_BackTab) { + if (d->tabChangesFocus) { + e->ignore(); + break; + } + } + + if ((autoFormatting() & AutoBulletList) && + textFormat() == Qt::RichText && cursor->index() == 0 + && !cursor->paragraph()->isListItem() + && (e->text()[0] == QLatin1Char('-') || e->text()[0] == QLatin1Char('*'))) { + clearUndoRedo(); + undoRedoInfo.type = UndoRedoInfo::Style; + undoRedoInfo.id = cursor->paragraph()->paragId(); + undoRedoInfo.eid = undoRedoInfo.id; + undoRedoInfo.styleInformation = Q3TextStyleCommand::readStyleInformation(doc, undoRedoInfo.id, undoRedoInfo.eid); + setParagType(Q3StyleSheetItem::DisplayListItem, Q3StyleSheetItem::ListDisc); + clearUndoRedo(); + drawCursor(false); + repaintChanged(); + drawCursor(true); + break; + } + if (overWrite && !cursor->atParagEnd() && !doc->hasSelection(Q3TextDocument::Standard)) { + doKeyboardAction(ActionDelete); + clearUndoRedoInfo = false; + } + QString t = e->text(); + insert(t, true, false); + break; + } else if (e->state() & Qt::ControlButton) { + switch (e->key()) { + case Qt::Key_C: case Qt::Key_F16: // Copy key on Sun keyboards + copy(); + break; + case Qt::Key_V: + paste(); + break; + case Qt::Key_X: + cut(); + break; + case Qt::Key_I: case Qt::Key_T: case Qt::Key_Tab: + if (!d->tabChangesFocus) + indent(); + break; + case Qt::Key_A: +#if defined(Q_WS_X11) + moveCursor(MoveLineStart, e->state() & Qt::ShiftButton); +#else + selectAll(true); +#endif + break; + case Qt::Key_B: + moveCursor(MoveBackward, e->state() & Qt::ShiftButton); + break; + case Qt::Key_F: + moveCursor(MoveForward, e->state() & Qt::ShiftButton); + break; + case Qt::Key_D: + if (doc->hasSelection(Q3TextDocument::Standard)) { + removeSelectedText(); + break; + } + doKeyboardAction(ActionDelete); + clearUndoRedoInfo = false; + break; + case Qt::Key_H: + if (doc->hasSelection(Q3TextDocument::Standard)) { + removeSelectedText(); + break; + } + if (!cursor->paragraph()->prev() && + cursor->atParagStart()) + break; + + doKeyboardAction(ActionBackspace); + clearUndoRedoInfo = false; + break; + case Qt::Key_E: + moveCursor(MoveLineEnd, e->state() & Qt::ShiftButton); + break; + case Qt::Key_N: + moveCursor(MoveDown, e->state() & Qt::ShiftButton); + break; + case Qt::Key_P: + moveCursor(MoveUp, e->state() & Qt::ShiftButton); + break; + case Qt::Key_Z: + if(e->state() & Qt::ShiftButton) + redo(); + else + undo(); + break; + case Qt::Key_Y: + redo(); + break; + case Qt::Key_K: + doKeyboardAction(ActionKill); + break; +#if defined(Q_WS_WIN) + case Qt::Key_Insert: + copy(); + break; + case Qt::Key_Delete: + del(); + break; +#endif + default: + unknownKey = false; + break; + } + } else { + unknownKey = true; + } + } + } + + emit cursorPositionChanged(cursor); + emit cursorPositionChanged(cursor->paragraph()->paragId(), cursor->index()); + if (clearUndoRedoInfo) + clearUndoRedo(); + changeIntervalTimer->start(100, true); + if (unknownKey) + e->ignore(); +} + +/*! + \reimp +*/ +void Q3TextEdit::inputMethodEvent(QInputMethodEvent *e) +{ + if (isReadOnly()) { + e->ignore(); + return; + } + + if (hasSelectedText()) + removeSelectedText(); + clearUndoRedo(); + undoRedoInfo.type = UndoRedoInfo::IME; + + bool oldupdate = updatesEnabled(); + if (oldupdate) + setUpdatesEnabled(false); + bool sigs_blocked = signalsBlocked(); + blockSignals(true); + const int preeditSelectionBase = 31900; + for (int i = 0; i < d->numPreeditSelections; ++i) + doc->removeSelection(preeditSelectionBase + i); + d->numPreeditSelections = 0; + + if (d->preeditLength > 0 && cursor->paragraph()) { + cursor->setIndex(d->preeditStart); + cursor->paragraph()->remove(d->preeditStart, d->preeditLength); + d->preeditStart = d->preeditLength = -1; + } + + if (!e->commitString().isEmpty() || e->replacementLength()) { + int c = cursor->index(); // cursor position after insertion of commit string + if (e->replacementStart() <= 0) + c += e->commitString().length() + qMin(-e->replacementStart(), e->replacementLength()); + cursor->setIndex(cursor->index() + e->replacementStart()); + doc->setSelectionStart(Q3TextDocument::Standard, *cursor); + cursor->setIndex(cursor->index() + e->replacementLength()); + doc->setSelectionEnd(Q3TextDocument::Standard, *cursor); + removeSelectedText(); + if (undoRedoInfo.type == UndoRedoInfo::IME) + undoRedoInfo.type = UndoRedoInfo::Invalid; + insert(e->commitString()); + undoRedoInfo.type = UndoRedoInfo::IME; + cursor->setIndex(c); + } + + if (!e->preeditString().isEmpty()) { + d->preeditStart = cursor->index(); + d->preeditLength = e->preeditString().length(); + insert(e->preeditString()); + cursor->setIndex(d->preeditStart); + + Q3TextCursor c = *cursor; + for (int i = 0; i < e->attributes().size(); ++i) { + const QInputMethodEvent::Attribute &a = e->attributes().at(i); + if (a.type == QInputMethodEvent::Cursor) + cursor->setIndex(cursor->index() + a.start); + else if (a.type != QInputMethodEvent::TextFormat) + continue; + QTextCharFormat f = qvariant_cast<QTextFormat>(a.value).toCharFormat(); + if (f.isValid()) { + Q3TextCursor c2 = c; + c2.setIndex(c.index() + a.start); + doc->setSelectionStart(preeditSelectionBase + d->numPreeditSelections, c2); + c2.setIndex(c.index() + a.start + a.length); + doc->setSelectionEnd(preeditSelectionBase + d->numPreeditSelections, c2); + + QColor c = f.hasProperty(QTextFormat::BackgroundBrush) ? f.background().color() : QColor(); + doc->setSelectionColor(preeditSelectionBase + d->numPreeditSelections, c); + c = f.hasProperty(QTextFormat::ForegroundBrush) ? f.foreground().color() : QColor(); + doc->setSelectionTextColor(preeditSelectionBase + d->numPreeditSelections, c); + if (f.fontUnderline()) { + Q3TextParagraph *par = cursor->paragraph(); + Q3TextFormat f(*par->string()->at(d->preeditStart).format()); + f.setUnderline(true); + Q3TextFormat *f2 = doc->formatCollection()->format(&f); + par->setFormat(d->preeditStart + a.start, a.length, f2); + } + ++d->numPreeditSelections; + } + } + } else { + undoRedoInfo.type = UndoRedoInfo::Invalid; + } + blockSignals(sigs_blocked); + if (oldupdate) + setUpdatesEnabled(true); + if (!e->commitString().isEmpty()) + emit textChanged(); + repaintChanged(); +} + + +static bool qtextedit_ignore_readonly = false; + +/*! + Executes keyboard action \a action. This is normally called by a + key event handler. +*/ + +void Q3TextEdit::doKeyboardAction(Q3TextEdit::KeyboardAction action) +{ + if (isReadOnly() && !qtextedit_ignore_readonly) + return; + + if (cursor->nestedDepth() != 0) + return; + + lastFormatted = cursor->paragraph(); + drawCursor(false); + bool doUpdateCurrentFormat = true; + + switch (action) { + case ActionWordDelete: + case ActionDelete: + if (action == ActionDelete && !cursor->atParagEnd()) { + if (undoEnabled) { + checkUndoRedoInfo(UndoRedoInfo::Delete); + if (!undoRedoInfo.valid()) { + undoRedoInfo.id = cursor->paragraph()->paragId(); + undoRedoInfo.index = cursor->index(); + undoRedoInfo.d->text.clear(); + } + int idx = cursor->index(); + do { + undoRedoInfo.d->text.insert(undoRedoInfo.d->text.length(), cursor->paragraph()->at(idx++), true); + } while (!cursor->paragraph()->string()->validCursorPosition(idx)); + } + cursor->remove(); + } else { + clearUndoRedo(); + doc->setSelectionStart(Q3TextDocument::Temp, *cursor); + if (action == ActionWordDelete && !cursor->atParagEnd()) { + cursor->gotoNextWord(); + } else { + cursor->gotoNextLetter(); + } + doc->setSelectionEnd(Q3TextDocument::Temp, *cursor); + removeSelectedText(Q3TextDocument::Temp); + } + break; + case ActionWordBackspace: + case ActionBackspace: + if (textFormat() == Qt::RichText + && (cursor->paragraph()->isListItem() + || cursor->paragraph()->listDepth()) + && cursor->index() == 0) { + if (undoEnabled) { + clearUndoRedo(); + undoRedoInfo.type = UndoRedoInfo::Style; + undoRedoInfo.id = cursor->paragraph()->paragId(); + undoRedoInfo.eid = undoRedoInfo.id; + undoRedoInfo.styleInformation = Q3TextStyleCommand::readStyleInformation(doc, undoRedoInfo.id, undoRedoInfo.eid); + } + int ldepth = cursor->paragraph()->listDepth(); + if (cursor->paragraph()->isListItem() && ldepth == 1) { + cursor->paragraph()->setListItem(false); + } else if (qMax(ldepth, 1) == 1) { + cursor->paragraph()->setListItem(false); + cursor->paragraph()->setListDepth(0); + } else { + cursor->paragraph()->setListDepth(ldepth - 1); + } + clearUndoRedo(); + lastFormatted = cursor->paragraph(); + repaintChanged(); + drawCursor(true); + return; + } + + if (action == ActionBackspace && !cursor->atParagStart()) { + if (undoEnabled) { + checkUndoRedoInfo(UndoRedoInfo::Delete); + if (!undoRedoInfo.valid()) { + undoRedoInfo.id = cursor->paragraph()->paragId(); + undoRedoInfo.index = cursor->index(); + undoRedoInfo.d->text.clear(); + } + undoRedoInfo.d->text.insert(0, cursor->paragraph()->at(cursor->index()-1), true); + undoRedoInfo.index = cursor->index()-1; + } + cursor->removePreviousChar(); + lastFormatted = cursor->paragraph(); + } else if (cursor->paragraph()->prev() + || (action == ActionWordBackspace + && !cursor->atParagStart())) { + clearUndoRedo(); + doc->setSelectionStart(Q3TextDocument::Temp, *cursor); + if (action == ActionWordBackspace && !cursor->atParagStart()) { + cursor->gotoPreviousWord(); + } else { + cursor->gotoPreviousLetter(); + } + doc->setSelectionEnd(Q3TextDocument::Temp, *cursor); + removeSelectedText(Q3TextDocument::Temp); + } + break; + case ActionReturn: + if (undoEnabled) { + checkUndoRedoInfo(UndoRedoInfo::Return); + if (!undoRedoInfo.valid()) { + undoRedoInfo.id = cursor->paragraph()->paragId(); + undoRedoInfo.index = cursor->index(); + undoRedoInfo.d->text.clear(); + } + undoRedoInfo.d->text += QString(QLatin1Char('\n')); + } + cursor->splitAndInsertEmptyParagraph(); + if (cursor->paragraph()->prev()) { + lastFormatted = cursor->paragraph()->prev(); + lastFormatted->invalidate(0); + } + doUpdateCurrentFormat = false; + break; + case ActionKill: + clearUndoRedo(); + doc->setSelectionStart(Q3TextDocument::Temp, *cursor); + if (cursor->atParagEnd()) + cursor->gotoNextLetter(); + else + cursor->setIndex(cursor->paragraph()->length() - 1); + doc->setSelectionEnd(Q3TextDocument::Temp, *cursor); + removeSelectedText(Q3TextDocument::Temp); + break; + } + + formatMore(); + repaintChanged(); + ensureCursorVisible(); + drawCursor(true); + if (doUpdateCurrentFormat) + updateCurrentFormat(); + setModified(); + emit textChanged(); +} + +void Q3TextEdit::readFormats(Q3TextCursor &c1, Q3TextCursor &c2, Q3TextString &text, bool fillStyles) +{ +#ifndef QT_NO_DATASTREAM + QDataStream styleStream(&undoRedoInfo.styleInformation, IO_WriteOnly); +#endif + c2.restoreState(); + c1.restoreState(); + int lastIndex = text.length(); + if (c1.paragraph() == c2.paragraph()) { + for (int i = c1.index(); i < c2.index(); ++i) + text.insert(lastIndex + i - c1.index(), c1.paragraph()->at(i), true); +#ifndef QT_NO_DATASTREAM + if (fillStyles) { + styleStream << (int) 1; + c1.paragraph()->writeStyleInformation(styleStream); + } +#endif + } else { + int i; + for (i = c1.index(); i < c1.paragraph()->length()-1; ++i) + text.insert(lastIndex++, c1.paragraph()->at(i), true); + int num = 2; // start and end, being different + text += QString(QLatin1Char('\n')); lastIndex++; + + if (c1.paragraph()->next() != c2.paragraph()) { + num += text.appendParagraphs(c1.paragraph()->next(), c2.paragraph()); + lastIndex = text.length(); + } + + for (i = 0; i < c2.index(); ++i) + text.insert(i + lastIndex, c2.paragraph()->at(i), true); +#ifndef QT_NO_DATASTREAM + if (fillStyles) { + styleStream << num; + for (Q3TextParagraph *p = c1.paragraph(); --num >= 0; p = p->next()) + p->writeStyleInformation(styleStream); + } +#endif + } +} + +/*! + Removes the selection \a selNum (by default 0). This does not + remove the selected text. + + \sa removeSelectedText() +*/ + +void Q3TextEdit::removeSelection(int selNum) +{ + doc->removeSelection(selNum); + repaintChanged(); +} + +/*! + Deletes the text of selection \a selNum (by default, the default + selection, 0). If there is no selected text nothing happens. + + \sa selectedText removeSelection() +*/ + +void Q3TextEdit::removeSelectedText(int selNum) +{ + Q3TextCursor c1 = doc->selectionStartCursor(selNum); + c1.restoreState(); + Q3TextCursor c2 = doc->selectionEndCursor(selNum); + c2.restoreState(); + + // ### no support for editing tables yet, plus security for broken selections + if (c1.nestedDepth() || c2.nestedDepth()) + return; + + for (int i = 0; i < (int)doc->numSelections(); ++i) { + if (i == selNum) + continue; + doc->removeSelection(i); + } + + drawCursor(false); + if (undoEnabled) { + checkUndoRedoInfo(UndoRedoInfo::RemoveSelected); + if (!undoRedoInfo.valid()) { + doc->selectionStart(selNum, undoRedoInfo.id, undoRedoInfo.index); + undoRedoInfo.d->text.clear(); + } + readFormats(c1, c2, undoRedoInfo.d->text, true); + } + + doc->removeSelectedText(selNum, cursor); + if (cursor->isValid()) { + lastFormatted = 0; // make sync a noop + ensureCursorVisible(); + lastFormatted = cursor->paragraph(); + formatMore(); + repaintContents(); + ensureCursorVisible(); + drawCursor(true); + clearUndoRedo(); +#if defined(Q_WS_WIN) + // there seems to be a problem with repainting or erasing the area + // of the scrollview which is not the contents on windows + if (contentsHeight() < visibleHeight()) + viewport()->repaint(0, contentsHeight(), visibleWidth(), visibleHeight() - contentsHeight()); +#endif +#ifndef QT_NO_CURSOR + viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor); +#endif + } else { + lastFormatted = doc->firstParagraph(); + delete cursor; + cursor = new Q3TextCursor(doc); + drawCursor(true); + repaintContents(); + } + setModified(); + emit textChanged(); + emit selectionChanged(); + emit copyAvailable(doc->hasSelection(Q3TextDocument::Standard)); +} + +/*! + Moves the text cursor according to \a action. This is normally + used by some key event handler. \a select specifies whether the + text between the current cursor position and the new position + should be selected. +*/ + +void Q3TextEdit::moveCursor(Q3TextEdit::CursorAction action, bool select) +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) + return; +#endif +#ifdef Q_WS_MAC + Q3TextCursor c1 = *cursor; + Q3TextCursor c2; +#endif + drawCursor(false); + if (select) { + if (!doc->hasSelection(Q3TextDocument::Standard)) + doc->setSelectionStart(Q3TextDocument::Standard, *cursor); + moveCursor(action); +#ifdef Q_WS_MAC + c2 = *cursor; + if (c1 == c2) + if (action == MoveDown || action == MovePgDown) + moveCursor(MoveEnd); + else if (action == MoveUp || action == MovePgUp) + moveCursor(MoveHome); +#endif + if (doc->setSelectionEnd(Q3TextDocument::Standard, *cursor)) { + cursor->paragraph()->document()->nextDoubleBuffered = true; + repaintChanged(); + } else { + drawCursor(true); + } + ensureCursorVisible(); + emit selectionChanged(); + emit copyAvailable(doc->hasSelection(Q3TextDocument::Standard)); + } else { +#ifdef Q_WS_MAC + Q3TextCursor cStart = doc->selectionStartCursor(Q3TextDocument::Standard); + Q3TextCursor cEnd = doc->selectionEndCursor(Q3TextDocument::Standard); + bool redraw = doc->removeSelection(Q3TextDocument::Standard); + if (redraw && action == MoveDown) + *cursor = cEnd; + else if (redraw && action == MoveUp) + *cursor = cStart; + if (redraw && action == MoveForward) + *cursor = cEnd; + else if (redraw && action == MoveBackward) + *cursor = cStart; + else + moveCursor(action); + c2 = *cursor; + if (c1 == c2) + if (action == MoveDown) + moveCursor(MoveEnd); + else if (action == MoveUp) + moveCursor(MoveHome); +#else + bool redraw = doc->removeSelection(Q3TextDocument::Standard); + moveCursor(action); +#endif + if (!redraw) { + ensureCursorVisible(); + drawCursor(true); + } else { + cursor->paragraph()->document()->nextDoubleBuffered = true; + repaintChanged(); + ensureCursorVisible(); + drawCursor(true); +#ifndef QT_NO_CURSOR + viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor); +#endif + } + if (redraw) { + emit copyAvailable(doc->hasSelection(Q3TextDocument::Standard)); + emit selectionChanged(); + } + } + + drawCursor(true); + updateCurrentFormat(); +} + +/*! + \overload +*/ + +void Q3TextEdit::moveCursor(Q3TextEdit::CursorAction action) +{ + resetInputContext(); + switch (action) { + case MoveBackward: + cursor->gotoPreviousLetter(); + break; + case MoveWordBackward: + cursor->gotoPreviousWord(); + break; + case MoveForward: + cursor->gotoNextLetter(); + break; + case MoveWordForward: + cursor->gotoNextWord(); + break; + case MoveUp: + cursor->gotoUp(); + break; + case MovePgUp: + cursor->gotoPageUp(visibleHeight()); + break; + case MoveDown: + cursor->gotoDown(); + break; + case MovePgDown: + cursor->gotoPageDown(visibleHeight()); + break; + case MoveLineStart: + cursor->gotoLineStart(); + break; + case MoveHome: + cursor->gotoHome(); + break; + case MoveLineEnd: + cursor->gotoLineEnd(); + break; + case MoveEnd: + ensureFormatted(doc->lastParagraph()); + cursor->gotoEnd(); + break; + } + updateCurrentFormat(); +} + +/*! + \reimp +*/ + +void Q3TextEdit::resizeEvent(QResizeEvent *e) +{ + Q3ScrollView::resizeEvent(e); + if (doc->visibleWidth() == 0) + doResize(); +} + +/*! + \reimp +*/ + +void Q3TextEdit::viewportResizeEvent(QResizeEvent *e) +{ + Q3ScrollView::viewportResizeEvent(e); + if (e->oldSize().width() != e->size().width()) { + bool stayAtBottom = e->oldSize().height() != e->size().height() && + contentsY() > 0 && contentsY() >= doc->height() - e->oldSize().height(); + doResize(); + if (stayAtBottom) + scrollToBottom(); + } +} + +/*! + Ensures that the cursor is visible by scrolling the text edit if + necessary. + + \sa setCursorPosition() +*/ + +void Q3TextEdit::ensureCursorVisible() +{ + // Not visible or the user is dragging the window, so don't position to caret yet + if (!updatesEnabled() || !isVisible() || isHorizontalSliderPressed() || isVerticalSliderPressed()) { + d->ensureCursorVisibleInShowEvent = true; + return; + } + sync(); + Q3TextStringChar *chr = cursor->paragraph()->at(cursor->index()); + int h = cursor->paragraph()->lineHeightOfChar(cursor->index()); + int x = cursor->paragraph()->rect().x() + chr->x + cursor->offsetX(); + int y = 0; int dummy; + cursor->paragraph()->lineHeightOfChar(cursor->index(), &dummy, &y); + y += cursor->paragraph()->rect().y() + cursor->offsetY(); + int w = 1; + ensureVisible(x, y + h / 2, w, h / 2 + 2); +} + +/*! + \internal +*/ +void Q3TextEdit::sliderReleased() +{ + if (d->ensureCursorVisibleInShowEvent && isVisible()) { + d->ensureCursorVisibleInShowEvent = false; + ensureCursorVisible(); + } +} + +/*! + \internal + + If \a visible is true, the cursor is shown; otherwise it is + hidden. +*/ +void Q3TextEdit::drawCursor(bool visible) +{ + d->cursorRepaintMode = true; + blinkCursorVisible = visible; + QRect r(cursor->topParagraph()->rect()); + if (!cursor->nestedDepth()) { + int h = cursor->paragraph()->lineHeightOfChar(cursor->index()); + r = QRect(r.x(), r.y() + cursor->y(), r.width(), h); + } + r.moveBy(-contentsX(), -contentsY()); + viewport()->update(r); +} + +enum { + IdUndo = 0, + IdRedo = 1, + IdCut = 2, + IdCopy = 3, + IdPaste = 4, + IdClear = 5, + IdSelectAll = 6 +}; + +/*! + \reimp +*/ +#ifndef QT_NO_WHEELEVENT +void Q3TextEdit::contentsWheelEvent(QWheelEvent *e) +{ + if (isReadOnly()) { + if (e->state() & Qt::ControlButton) { + if (e->delta() > 0) + zoomOut(); + else if (e->delta() < 0) + zoomIn(); + return; + } + } + Q3ScrollView::contentsWheelEvent(e); +} +#endif + +/*! + \reimp +*/ + +void Q3TextEdit::contentsMousePressEvent(QMouseEvent *e) +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + optimMousePressEvent(e); + return; + } +#endif + +#if !defined(QT_NO_IM) + if (e->button() == Qt::LeftButton && d->preeditLength > 0 && cursor->paragraph()) { + Q3TextCursor c = *cursor; + placeCursor(e->pos(), &c, false); + inputContext()->mouseHandler(c.index() - d->preeditStart, e); + if (d->preeditLength > 0) + return; + } +#endif + + if (d->trippleClickTimer->isActive() && + (e->globalPos() - d->trippleClickPoint).manhattanLength() < + QApplication::startDragDistance()) { + Q3TextCursor c1 = *cursor; + Q3TextCursor c2 = *cursor; + c1.gotoLineStart(); + c2.gotoLineEnd(); + doc->setSelectionStart(Q3TextDocument::Standard, c1); + doc->setSelectionEnd(Q3TextDocument::Standard, c2); + *cursor = c2; + repaintChanged(); + mousePressed = true; + return; + } + + clearUndoRedo(); + Q3TextCursor oldCursor = *cursor; + Q3TextCursor c = *cursor; + mousePos = e->pos(); + mightStartDrag = false; + pressedLink.clear(); + d->pressedName.clear(); + + if (e->button() == Qt::LeftButton) { + mousePressed = true; + drawCursor(false); + placeCursor(e->pos()); + ensureCursorVisible(); + + if (isReadOnly() && linksEnabled()) { + Q3TextCursor c = *cursor; + placeCursor(e->pos(), &c, true); + if (c.paragraph() && c.paragraph()->at(c.index()) && + c.paragraph()->at(c.index())->isAnchor()) { + pressedLink = c.paragraph()->at(c.index())->anchorHref(); + d->pressedName = c.paragraph()->at(c.index())->anchorName(); + } + } + +#ifndef QT_NO_DRAGANDDROP + if (doc->inSelection(Q3TextDocument::Standard, e->pos())) { + mightStartDrag = true; + drawCursor(true); + dragStartTimer->start(QApplication::startDragTime(), true); + dragStartPos = e->pos(); + return; + } +#endif + + bool redraw = false; + if (doc->hasSelection(Q3TextDocument::Standard)) { + if (!(e->state() & Qt::ShiftButton)) { + redraw = doc->removeSelection(Q3TextDocument::Standard); + doc->setSelectionStart(Q3TextDocument::Standard, *cursor); + } else { + redraw = doc->setSelectionEnd(Q3TextDocument::Standard, *cursor) || redraw; + } + } else { + if (isReadOnly() || !(e->state() & Qt::ShiftButton)) { + doc->setSelectionStart(Q3TextDocument::Standard, *cursor); + } else { + doc->setSelectionStart(Q3TextDocument::Standard, c); + redraw = doc->setSelectionEnd(Q3TextDocument::Standard, *cursor) || redraw; + } + } + + for (int i = 1; i < doc->numSelections(); ++i) // start with 1 as we don't want to remove the Standard-Selection + redraw = doc->removeSelection(i) || redraw; + + if (!redraw) { + drawCursor(true); + } else { + repaintChanged(); +#ifndef QT_NO_CURSOR + viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor); +#endif + } + } else if (e->button() == Qt::MidButton) { + bool redraw = doc->removeSelection(Q3TextDocument::Standard); + if (!redraw) { + drawCursor(true); + } else { + repaintChanged(); +#ifndef QT_NO_CURSOR + viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor); +#endif + } + } + + if (*cursor != oldCursor) + updateCurrentFormat(); +} + +/*! + \reimp +*/ + +void Q3TextEdit::contentsMouseMoveEvent(QMouseEvent *e) +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + optimMouseMoveEvent(e); + return; + } +#endif + +#if !defined(QT_NO_IM) + if (d->preeditLength > 0) + return; +#endif + + if (mousePressed) { +#ifndef QT_NO_DRAGANDDROP + if (mightStartDrag) { + dragStartTimer->stop(); + if ((e->pos() - dragStartPos).manhattanLength() > QApplication::startDragDistance()) + startDrag(); +#ifndef QT_NO_CURSOR + if (!isReadOnly()) + viewport()->setCursor(Qt::IBeamCursor); +#endif + return; + } +#endif + mousePos = e->pos(); + handleMouseMove(mousePos); + oldMousePos = mousePos; + } + +#ifndef QT_NO_CURSOR + if (!isReadOnly() && !mousePressed) { + if (doc->hasSelection(Q3TextDocument::Standard) && doc->inSelection(Q3TextDocument::Standard, e->pos())) + viewport()->setCursor(Qt::ArrowCursor); + else + viewport()->setCursor(Qt::IBeamCursor); + } +#endif + updateCursor(e->pos()); +} + +void Q3TextEdit::copyToClipboard() +{ +#ifndef QT_NO_CLIPBOARD + if (QApplication::clipboard()->supportsSelection()) { + d->clipboard_mode = QClipboard::Selection; + + // don't listen to selection changes + disconnect(QApplication::clipboard(), SIGNAL(selectionChanged()), this, 0); + copy(); + // listen to selection changes + connect(QApplication::clipboard(), SIGNAL(selectionChanged()), + this, SLOT(clipboardChanged())); + + d->clipboard_mode = QClipboard::Clipboard; + } +#endif +} + +/*! + \reimp +*/ + +void Q3TextEdit::contentsMouseReleaseEvent(QMouseEvent * e) +{ + if (!inDoubleClick) { // could be the release of a dblclick + int para = 0; + int index = charAt(e->pos(), ¶); + emit clicked(para, index); + } +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + optimMouseReleaseEvent(e); + return; + } +#endif + Q3TextCursor oldCursor = *cursor; + if (scrollTimer->isActive()) + scrollTimer->stop(); +#ifndef QT_NO_DRAGANDDROP + if (dragStartTimer->isActive()) + dragStartTimer->stop(); + if (mightStartDrag) { + selectAll(false); + mousePressed = false; + } +#endif + if (mousePressed) { + mousePressed = false; + copyToClipboard(); + } +#ifndef QT_NO_CLIPBOARD + else if (e->button() == Qt::MidButton && !isReadOnly()) { + // only do middle-click pasting on systems that have selections (ie. X11) + if (QApplication::clipboard()->supportsSelection()) { + drawCursor(false); + placeCursor(e->pos()); + ensureCursorVisible(); + doc->setSelectionStart(Q3TextDocument::Standard, oldCursor); + bool redraw = false; + if (doc->hasSelection(Q3TextDocument::Standard)) { + redraw = doc->removeSelection(Q3TextDocument::Standard); + doc->setSelectionStart(Q3TextDocument::Standard, *cursor); + } else { + doc->setSelectionStart(Q3TextDocument::Standard, *cursor); + } + // start with 1 as we don't want to remove the Standard-Selection + for (int i = 1; i < doc->numSelections(); ++i) + redraw = doc->removeSelection(i) || redraw; + if (!redraw) { + drawCursor(true); + } else { + repaintChanged(); +#ifndef QT_NO_CURSOR + viewport()->setCursor(Qt::IBeamCursor); +#endif + } + d->clipboard_mode = QClipboard::Selection; + paste(); + d->clipboard_mode = QClipboard::Clipboard; + } + } +#endif + emit cursorPositionChanged(cursor); + emit cursorPositionChanged(cursor->paragraph()->paragId(), cursor->index()); + if (oldCursor != *cursor) + updateCurrentFormat(); + inDoubleClick = false; + +#ifndef QT_NO_NETWORKPROTOCOL + if (( (!onLink.isEmpty() && onLink == pressedLink) + || (!d->onName.isEmpty() && d->onName == d->pressedName)) + && linksEnabled()) { + if (!onLink.isEmpty()) { + QUrl u = QUrl(doc->context()).resolved(onLink); + emitLinkClicked(u.toString(QUrl::None)); + } + if (Q3TextBrowser *browser = qobject_cast<Q3TextBrowser*>(this)) + emit browser->anchorClicked(d->onName, onLink); + + // emitting linkClicked() may result in that the cursor winds + // up hovering over a different valid link - check this and + // set the appropriate cursor shape + updateCursor(e->pos()); + } +#endif + drawCursor(true); + if (!doc->hasSelection(Q3TextDocument::Standard, true)) + doc->removeSelection(Q3TextDocument::Standard); + + emit copyAvailable(doc->hasSelection(Q3TextDocument::Standard)); + emit selectionChanged(); +} + +/*! + \reimp +*/ + +void Q3TextEdit::contentsMouseDoubleClickEvent(QMouseEvent * e) +{ + if (e->button() != Qt::LeftButton) { + e->ignore(); + return; + } +#if !defined(QT_NO_IM) + if (d->preeditLength > 0) + return; +#endif + + int para = 0; + int index = charAt(e->pos(), ¶); +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + QString str = d->od->lines[LOGOFFSET(para)]; + int startIdx = index, endIdx = index, i; + if (!str[index].isSpace()) { + i = startIdx; + // find start of word + while (i >= 0 && !str[i].isSpace()) { + startIdx = i--; + } + i = endIdx; + // find end of word.. + while (i < str.length() && !str[i].isSpace()) { + endIdx = ++i; + } + // ..and start of next + while (i < str.length() && str[i].isSpace()) { + endIdx = ++i; + } + optimSetSelection(para, startIdx, para, endIdx); + repaintContents(); + } + } else +#endif + { + Q3TextCursor c1 = *cursor; + Q3TextCursor c2 = *cursor; +#if defined(Q_OS_MAC) + Q3TextParagraph *para = cursor->paragraph(); + if (cursor->isValid()) { + if (para->at(cursor->index())->c.isLetterOrNumber()) { + while (c1.index() > 0 && + c1.paragraph()->at(c1.index()-1)->c.isLetterOrNumber()) + c1.gotoPreviousLetter(); + while (c2.paragraph()->at(c2.index())->c.isLetterOrNumber() && + !c2.atParagEnd()) + c2.gotoNextLetter(); + } else if (para->at(cursor->index())->c.isSpace()) { + while (c1.index() > 0 && + c1.paragraph()->at(c1.index()-1)->c.isSpace()) + c1.gotoPreviousLetter(); + while (c2.paragraph()->at(c2.index())->c.isSpace() && + !c2.atParagEnd()) + c2.gotoNextLetter(); + } else if (!c2.atParagEnd()) { + c2.gotoNextLetter(); + } + } +#else + if (cursor->index() > 0 && !cursor->paragraph()->at(cursor->index()-1)->c.isSpace()) + c1.gotoPreviousWord(); + if (!cursor->paragraph()->at(cursor->index())->c.isSpace() && !cursor->atParagEnd()) + c2.gotoNextWord(); +#endif + doc->setSelectionStart(Q3TextDocument::Standard, c1); + doc->setSelectionEnd(Q3TextDocument::Standard, c2); + + *cursor = c2; + + repaintChanged(); + + d->trippleClickTimer->start(qApp->doubleClickInterval(), true); + d->trippleClickPoint = e->globalPos(); + } + inDoubleClick = true; + mousePressed = true; + emit doubleClicked(para, index); +} + +#ifndef QT_NO_DRAGANDDROP + +/*! + \reimp +*/ + +void Q3TextEdit::contentsDragEnterEvent(QDragEnterEvent *e) +{ + if (isReadOnly() || !Q3TextDrag::canDecode(e)) { + e->ignore(); + return; + } + e->acceptAction(); + inDnD = true; +} + +/*! + \reimp +*/ + +void Q3TextEdit::contentsDragMoveEvent(QDragMoveEvent *e) +{ + if (isReadOnly() || !Q3TextDrag::canDecode(e)) { + e->ignore(); + return; + } + drawCursor(false); + placeCursor(e->pos(), cursor); + drawCursor(true); + e->acceptAction(); +} + +/*! + \reimp +*/ + +void Q3TextEdit::contentsDragLeaveEvent(QDragLeaveEvent *) +{ + drawCursor(false); + inDnD = false; +} + +/*! + \reimp +*/ + +void Q3TextEdit::contentsDropEvent(QDropEvent *e) +{ + if (isReadOnly()) + return; + inDnD = false; + e->acceptAction(); + bool intern = false; + if (Q3RichTextDrag::canDecode(e)) { + bool hasSel = doc->hasSelection(Q3TextDocument::Standard); + bool internalDrag = e->source() == this || e->source() == viewport(); + int dropId, dropIndex; + Q3TextCursor insertCursor = *cursor; + dropId = cursor->paragraph()->paragId(); + dropIndex = cursor->index(); + if (hasSel && internalDrag) { + Q3TextCursor c1, c2; + int selStartId, selStartIndex; + int selEndId, selEndIndex; + c1 = doc->selectionStartCursor(Q3TextDocument::Standard); + c1.restoreState(); + c2 = doc->selectionEndCursor(Q3TextDocument::Standard); + c2.restoreState(); + selStartId = c1.paragraph()->paragId(); + selStartIndex = c1.index(); + selEndId = c2.paragraph()->paragId(); + selEndIndex = c2.index(); + if (((dropId > selStartId) || + (dropId == selStartId && dropIndex > selStartIndex)) && + ((dropId < selEndId) || + (dropId == selEndId && dropIndex <= selEndIndex))) + insertCursor = c1; + if (dropId == selEndId && dropIndex > selEndIndex) { + insertCursor = c1; + if (selStartId == selEndId) { + insertCursor.setIndex(dropIndex - + (selEndIndex - selStartIndex)); + } else { + insertCursor.setIndex(dropIndex - selEndIndex + + selStartIndex); + } + } + } + + if (internalDrag && e->action() == QDropEvent::Move) { + removeSelectedText(); + intern = true; + doc->removeSelection(Q3TextDocument::Standard); + } else { + doc->removeSelection(Q3TextDocument::Standard); +#ifndef QT_NO_CURSOR + viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor); +#endif + } + drawCursor(false); + cursor->setParagraph(insertCursor.paragraph()); + cursor->setIndex(insertCursor.index()); + drawCursor(true); + if (!cursor->nestedDepth()) { + QString subType = QLatin1String("plain"); + if (textFormat() != Qt::PlainText) { + if (e->provides("application/x-qrichtext")) + subType = QLatin1String("x-qrichtext"); + } +#ifndef QT_NO_CLIPBOARD + pasteSubType(subType.toLatin1(), e); +#endif + // emit appropriate signals. + emit selectionChanged(); + emit cursorPositionChanged(cursor); + emit cursorPositionChanged(cursor->paragraph()->paragId(), cursor->index()); + } else { + if (intern) + undo(); + e->ignore(); + } + } +} + +#endif + +/*! + \reimp +*/ +void Q3TextEdit::contentsContextMenuEvent(QContextMenuEvent *e) +{ + clearUndoRedo(); + mousePressed = false; + + e->accept(); +#ifndef QT_NO_POPUPMENU + Q3PopupMenu *popup = createPopupMenu(e->pos()); + if (!popup) + popup = createPopupMenu(); + if (!popup) + return; + int r = popup->exec(e->globalPos(), -1); + delete popup; + + if (r == d->id[IdClear]) + clear(); + else if (r == d->id[IdSelectAll]) { + selectAll(); +#ifndef QT_NO_CLIPBOARD + // if the clipboard support selections, put the newly selected text into + // the clipboard + if (QApplication::clipboard()->supportsSelection()) { + d->clipboard_mode = QClipboard::Selection; + + // don't listen to selection changes + disconnect(QApplication::clipboard(), SIGNAL(selectionChanged()), this, 0); + copy(); + // listen to selection changes + connect(QApplication::clipboard(), SIGNAL(selectionChanged()), + this, SLOT(clipboardChanged())); + + d->clipboard_mode = QClipboard::Clipboard; + } +#endif + } else if (r == d->id[IdUndo]) + undo(); + else if (r == d->id[IdRedo]) + redo(); +#ifndef QT_NO_CLIPBOARD + else if (r == d->id[IdCut]) + cut(); + else if (r == d->id[IdCopy]) + copy(); + else if (r == d->id[IdPaste]) + paste(); +#endif +#endif +} + + +void Q3TextEdit::autoScrollTimerDone() +{ + if (mousePressed) + handleMouseMove( viewportToContents(viewport()->mapFromGlobal(QCursor::pos()) )); +} + +void Q3TextEdit::handleMouseMove(const QPoint& pos) +{ + if (!mousePressed) + return; + + if ((!scrollTimer->isActive() && pos.y() < contentsY()) || pos.y() > contentsY() + visibleHeight()) + scrollTimer->start(100, false); + else if (scrollTimer->isActive() && pos.y() >= contentsY() && pos.y() <= contentsY() + visibleHeight()) + scrollTimer->stop(); + + drawCursor(false); + Q3TextCursor oldCursor = *cursor; + + placeCursor(pos); + + if (inDoubleClick) { + Q3TextCursor cl = *cursor; + cl.gotoPreviousWord(); + Q3TextCursor cr = *cursor; + cr.gotoNextWord(); + + int diff = QABS(oldCursor.paragraph()->at(oldCursor.index())->x - mousePos.x()); + int ldiff = QABS(cl.paragraph()->at(cl.index())->x - mousePos.x()); + int rdiff = QABS(cr.paragraph()->at(cr.index())->x - mousePos.x()); + + + if (cursor->paragraph()->lineStartOfChar(cursor->index()) != + oldCursor.paragraph()->lineStartOfChar(oldCursor.index())) + diff = 0xFFFFFF; + + if (rdiff < diff && rdiff < ldiff) + *cursor = cr; + else if (ldiff < diff && ldiff < rdiff) + *cursor = cl; + else + *cursor = oldCursor; + + } + ensureCursorVisible(); + + bool redraw = false; + if (doc->hasSelection(Q3TextDocument::Standard)) { + redraw = doc->setSelectionEnd(Q3TextDocument::Standard, *cursor) || redraw; + } + + if (!redraw) { + drawCursor(true); + } else { + repaintChanged(); + drawCursor(true); + } + + if (currentFormat && currentFormat->key() != cursor->paragraph()->at(cursor->index())->format()->key()) { + currentFormat->removeRef(); + currentFormat = doc->formatCollection()->format(cursor->paragraph()->at(cursor->index())->format()); + if (currentFormat->isMisspelled()) { + currentFormat->removeRef(); + currentFormat = doc->formatCollection()->format(currentFormat->font(), currentFormat->color()); + } + emit currentFontChanged(currentFormat->font()); + emit currentColorChanged(currentFormat->color()); + emit currentVerticalAlignmentChanged((VerticalAlignment)currentFormat->vAlign()); + } + + if (currentAlignment != cursor->paragraph()->alignment()) { + currentAlignment = cursor->paragraph()->alignment(); + block_set_alignment = true; + emit currentAlignmentChanged(currentAlignment); + block_set_alignment = false; + } +} + +/*! \internal */ + +void Q3TextEdit::placeCursor(const QPoint &pos, Q3TextCursor *c, bool link) +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) + return; +#endif + if (!c) + c = cursor; + + if(c == cursor) + resetInputContext(); + c->restoreState(); + Q3TextParagraph *s = doc->firstParagraph(); + c->place(pos, s, link); +} + + +QVariant Q3TextEdit::inputMethodQuery(Qt::InputMethodQuery query) const +{ + Q3TextCursor c(*cursor); + + switch(query) { + case Qt::ImMicroFocus: { + int h = c.paragraph()->lineHeightOfChar(cursor->index()); + return QRect(c.x() - contentsX() + frameWidth(), + c.y() + cursor->paragraph()->rect().y() - contentsY() + frameWidth(), 1, h); + } + case Qt::ImFont: + return c.paragraph()->at(c.index())->format()->font(); + default: + // ##### fix the others! + return QWidget::inputMethodQuery(query); + } +} + + + +void Q3TextEdit::formatMore() +{ + if (!lastFormatted) + return; + + int bottom = contentsHeight(); + int lastTop = -1; + int lastBottom = -1; + int to = 20; + bool firstVisible = false; + QRect cr(contentsX(), contentsY(), visibleWidth(), visibleHeight()); + for (int i = 0; lastFormatted && + (i < to || (firstVisible && lastTop < contentsY()+height())); + i++) { + lastFormatted->format(); + lastTop = lastFormatted->rect().top(); + lastBottom = lastFormatted->rect().bottom(); + if (i == 0) + firstVisible = lastBottom < cr.bottom(); + bottom = qMax(bottom, lastBottom); + lastFormatted = lastFormatted->next(); + } + + if (bottom > contentsHeight()) { + resizeContents(contentsWidth(), qMax(doc->height(), bottom)); + } else if (!lastFormatted && lastBottom < contentsHeight()) { + resizeContents(contentsWidth(), qMax(doc->height(), lastBottom)); + if (contentsHeight() < visibleHeight()) + updateContents(0, contentsHeight(), visibleWidth(), + visibleHeight() - contentsHeight()); + } + + if (lastFormatted) + formatTimer->start(interval, true); + else + interval = qMax(0, interval); +} + +void Q3TextEdit::doResize() +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (!d->optimMode) +#endif + { + if (wrapMode == FixedPixelWidth) + return; + doc->setMinimumWidth(-1); + resizeContents(0, 0); + doc->setWidth(visibleWidth()); + doc->invalidate(); + lastFormatted = doc->firstParagraph(); + interval = 0; + formatMore(); + } + repaintContents(); +} + +/*! \internal */ + +void Q3TextEdit::doChangeInterval() +{ + interval = 0; +} + +/*! + \reimp +*/ + +bool Q3TextEdit::eventFilter(QObject *o, QEvent *e) +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (!d->optimMode && (o == this || o == viewport())) { +#else + if (o == this || o == viewport()) { +#endif + if (d->cursorBlinkActive && e->type() == QEvent::FocusIn) { + if (QApplication::cursorFlashTime() > 0) + blinkTimer->start(QApplication::cursorFlashTime() / 2); + drawCursor(true); + } else if (e->type() == QEvent::FocusOut) { + blinkTimer->stop(); + drawCursor(false); + } + } + + if (o == this && e->type() == QEvent::PaletteChange) { + QColor old(viewport()->palette().color(QPalette::Text)); + if (old != palette().color(QPalette::Text)) { + QColor c(palette().color(QPalette::Text)); + doc->setMinimumWidth(-1); + doc->setDefaultFormat(doc->formatCollection()->defaultFormat()->font(), c); + lastFormatted = doc->firstParagraph(); + formatMore(); + repaintChanged(); + } + } + + return Q3ScrollView::eventFilter(o, e); +} + +/*! + Inserts the given \a text. If \a indent is true the paragraph that + contains the text is reindented; if \a checkNewLine is true the \a + text is checked for newlines and relaid out. If \a removeSelected + is true and there is a selection, the insertion replaces the + selected text. + */ +void Q3TextEdit::insert(const QString &text, bool indent, + bool checkNewLine, bool removeSelected) +{ + uint f = 0; + if (indent) + f |= RedoIndentation; + if (checkNewLine) + f |= CheckNewLines; + if (removeSelected) + f |= RemoveSelected; + insert(text, f); +} + +/*! + Inserts \a text at the current cursor position. + + The \a insertionFlags define how the text is inserted. If \c + RedoIndentation is set, the paragraph is re-indented. If \c + CheckNewLines is set, newline characters in \a text result in hard + line breaks (i.e. new paragraphs). If \c checkNewLine is not set, + the behavior of the editor is undefined if the \a text contains + newlines. (It is not possible to change Q3TextEdit's newline handling + behavior, but you can use QString::replace() to preprocess text + before inserting it.) If \c RemoveSelected is set, any selected + text (in selection 0) is removed before the text is inserted. + + The default flags are \c CheckNewLines | \c RemoveSelected. + + If the widget is in Qt::LogText mode this function will do nothing. + + \sa paste() pasteSubType() +*/ + + +void Q3TextEdit::insert(const QString &text, uint insertionFlags) +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) + return; +#endif + + if (cursor->nestedDepth() != 0) // #### for 3.0, disable editing of tables as this is not advanced enough + return; + + bool indent = insertionFlags & RedoIndentation; + bool checkNewLine = insertionFlags & CheckNewLines; + bool removeSelected = insertionFlags & RemoveSelected; + QString txt(text); + drawCursor(false); + if (!isReadOnly() && doc->hasSelection(Q3TextDocument::Standard) && removeSelected) + removeSelectedText(); + Q3TextCursor c2 = *cursor; + int oldLen = 0; + + if ( undoEnabled && !isReadOnly() && undoRedoInfo.type != UndoRedoInfo::IME ) { + checkUndoRedoInfo(UndoRedoInfo::Insert); + + // If we are inserting at the end of the previous insertion, we keep this in + // the same undo/redo command. Otherwise, we separate them in two different commands. + if (undoRedoInfo.valid() && undoRedoInfo.index + undoRedoInfo.d->text.length() != cursor->index()) { + clearUndoRedo(); + undoRedoInfo.type = UndoRedoInfo::Insert; + } + + if (!undoRedoInfo.valid()) { + undoRedoInfo.id = cursor->paragraph()->paragId(); + undoRedoInfo.index = cursor->index(); + undoRedoInfo.d->text.clear(); + } + oldLen = undoRedoInfo.d->text.length(); + } + + lastFormatted = checkNewLine && cursor->paragraph()->prev() ? + cursor->paragraph()->prev() : cursor->paragraph(); + Q3TextCursor oldCursor = *cursor; + cursor->insert(txt, checkNewLine); + if (doc->useFormatCollection() && !doc->preProcessor()) { + doc->setSelectionStart(Q3TextDocument::Temp, oldCursor); + doc->setSelectionEnd(Q3TextDocument::Temp, *cursor); + doc->setFormat(Q3TextDocument::Temp, currentFormat, Q3TextFormat::Format); + doc->removeSelection(Q3TextDocument::Temp); + } + + if (indent && (txt == QString(QLatin1Char('{')) || txt == QString(QLatin1Char('}')) || txt == QString(QLatin1Char(':')) || txt == QString(QLatin1Char('#')))) + cursor->indent(); + formatMore(); + repaintChanged(); + ensureCursorVisible(); + drawCursor(true); + + if ( undoEnabled && !isReadOnly() && undoRedoInfo.type != UndoRedoInfo::IME ) { + undoRedoInfo.d->text += txt; + if (!doc->preProcessor()) { + for (int i = 0; i < (int)txt.length(); ++i) { + if (txt[i] != QLatin1Char('\n') && c2.paragraph()->at(c2.index())->format()) { + c2.paragraph()->at(c2.index())->format()->addRef(); + undoRedoInfo.d->text. + setFormat(oldLen + i, + c2.paragraph()->at(c2.index())->format(), true); + } + c2.gotoNextLetter(); + } + } + } + + if (!removeSelected) { + doc->setSelectionStart(Q3TextDocument::Standard, oldCursor); + doc->setSelectionEnd(Q3TextDocument::Standard, *cursor); + repaintChanged(); + } + + setModified(); + emit textChanged(); +} + +/*! + Inserts \a text in the paragraph \a para at position \a index. +*/ + +void Q3TextEdit::insertAt(const QString &text, int para, int index) +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + optimInsert(text, para, index); + return; + } +#endif + Q3TextParagraph *p = doc->paragAt(para); + if (!p) + return; + removeSelection(Q3TextDocument::Standard); + Q3TextCursor tmp = *cursor; + cursor->setParagraph(p); + cursor->setIndex(index); + insert(text, false, true, false); + *cursor = tmp; + removeSelection(Q3TextDocument::Standard); +} + +/*! + Inserts \a text as a new paragraph at position \a para. If \a para + is -1, the text is appended. Use append() if the append operation + is performance critical. +*/ + +void Q3TextEdit::insertParagraph(const QString &text, int para) +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + optimInsert(text + QLatin1Char('\n'), para, 0); + return; + } +#endif + for (int i = 0; i < (int)doc->numSelections(); ++i) + doc->removeSelection(i); + + Q3TextParagraph *p = doc->paragAt(para); + + bool append = !p; + if (!p) + p = doc->lastParagraph(); + + Q3TextCursor old = *cursor; + drawCursor(false); + + cursor->setParagraph(p); + cursor->setIndex(0); + clearUndoRedo(); + qtextedit_ignore_readonly = true; + if (append && cursor->paragraph()->length() > 1) { + cursor->setIndex(cursor->paragraph()->length() - 1); + doKeyboardAction(ActionReturn); + } + insert(text, false, true, true); + doKeyboardAction(ActionReturn); + qtextedit_ignore_readonly = false; + + drawCursor(false); + *cursor = old; + drawCursor(true); + + repaintChanged(); +} + +/*! + Removes the paragraph \a para. +*/ + +void Q3TextEdit::removeParagraph(int para) +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) + return; +#endif + Q3TextParagraph *p = doc->paragAt(para); + if (!p) + return; + + for (int i = 0; i < doc->numSelections(); ++i) + doc->removeSelection(i); + + Q3TextCursor start(doc); + Q3TextCursor end(doc); + start.setParagraph(p); + start.setIndex(0); + end.setParagraph(p); + end.setIndex(p->length() - 1); + + if (!(p == doc->firstParagraph() && p == doc->lastParagraph())) { + if (p->next()) { + end.setParagraph(p->next()); + end.setIndex(0); + } else if (p->prev()) { + start.setParagraph(p->prev()); + start.setIndex(p->prev()->length() - 1); + } + } + + doc->setSelectionStart(Q3TextDocument::Temp, start); + doc->setSelectionEnd(Q3TextDocument::Temp, end); + removeSelectedText(Q3TextDocument::Temp); +} + +/*! + Undoes the last operation. + + If there is no operation to undo, i.e. there is no undo step in + the undo/redo history, nothing happens. + + \sa undoAvailable() redo() undoDepth() +*/ + +void Q3TextEdit::undo() +{ + clearUndoRedo(); + if (isReadOnly() || !doc->commands()->isUndoAvailable() || !undoEnabled) + return; + + for (int i = 0; i < (int)doc->numSelections(); ++i) + doc->removeSelection(i); + +#ifndef QT_NO_CURSOR + viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor); +#endif + + clearUndoRedo(); + drawCursor(false); + Q3TextCursor *c = doc->undo(cursor); + if (!c) { + drawCursor(true); + return; + } + lastFormatted = 0; + repaintChanged(); + ensureCursorVisible(); + drawCursor(true); + setModified(); + // ### If we get back to a completely blank textedit, it + // is possible that cursor is invalid and further actions + // might not fix the problem, so reset the cursor here. + // This is copied from removeSeletedText(), it might be + // okay to just call that. + if (!cursor->isValid()) { + delete cursor; + cursor = new Q3TextCursor(doc); + drawCursor(true); + repaintContents(); + } + emit undoAvailable(isUndoAvailable()); + emit redoAvailable(isRedoAvailable()); + emit textChanged(); +} + +/*! + Redoes the last operation. + + If there is no operation to redo, i.e. there is no redo step in + the undo/redo history, nothing happens. + + \sa redoAvailable() undo() undoDepth() +*/ + +void Q3TextEdit::redo() +{ + if (isReadOnly() || !doc->commands()->isRedoAvailable() || !undoEnabled) + return; + + for (int i = 0; i < (int)doc->numSelections(); ++i) + doc->removeSelection(i); + +#ifndef QT_NO_CURSOR + viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor); +#endif + + clearUndoRedo(); + drawCursor(false); + Q3TextCursor *c = doc->redo(cursor); + if (!c) { + drawCursor(true); + return; + } + lastFormatted = 0; + ensureCursorVisible(); + repaintChanged(); + ensureCursorVisible(); + drawCursor(true); + setModified(); + emit undoAvailable(isUndoAvailable()); + emit redoAvailable(isRedoAvailable()); + emit textChanged(); +} + +/*! + Pastes the text from the clipboard into the text edit at the + current cursor position. Only plain text is pasted. + + If there is no text in the clipboard nothing happens. + + \sa pasteSubType() cut() Q3TextEdit::copy() +*/ + +void Q3TextEdit::paste() +{ +#ifndef QT_NO_MIMECLIPBOARD + if (isReadOnly()) + return; + QString subType = QLatin1String("plain"); + if (textFormat() != Qt::PlainText) { + QMimeSource *m = QApplication::clipboard()->data(d->clipboard_mode); + if (!m) + return; + if (m->provides("application/x-qrichtext")) + subType = QLatin1String("x-qrichtext"); + } + + pasteSubType(subType.toLatin1()); +#endif +} + +void Q3TextEdit::checkUndoRedoInfo(UndoRedoInfo::Type t) +{ + if (undoRedoInfo.valid() && t != undoRedoInfo.type) { + clearUndoRedo(); + } + undoRedoInfo.type = t; +} + +/*! + Repaints any paragraphs that have changed. + + Although used extensively internally you shouldn't need to call + this yourself. +*/ +void Q3TextEdit::repaintChanged() +{ + if (!updatesEnabled() || !viewport()->updatesEnabled()) + return; + + if (doc->firstParagraph()) + lastFormatted = doc->firstParagraph(); + updateContents(); // good enough until this class is rewritten +} + +#ifndef QT_NO_MIME +Q3TextDrag *Q3TextEdit::dragObject(QWidget *parent) const +{ + if (!doc->hasSelection(Q3TextDocument::Standard) || + doc->selectedText(Q3TextDocument::Standard).isEmpty()) + return 0; + if (textFormat() != Qt::RichText) + return new Q3TextDrag(doc->selectedText(Q3TextDocument::Standard), parent); + Q3RichTextDrag *drag = new Q3RichTextDrag(parent); + drag->setPlainText(doc->selectedText(Q3TextDocument::Standard)); + drag->setRichText(doc->selectedText(Q3TextDocument::Standard, true)); + return drag; +} +#endif + +/*! + Copies the selected text (from selection 0) to the clipboard and + deletes it from the text edit. + + If there is no selected text (in selection 0) nothing happens. + + \sa Q3TextEdit::copy() paste() pasteSubType() +*/ + +void Q3TextEdit::cut() +{ + if (isReadOnly()) + return; + normalCopy(); + removeSelectedText(); +} + +void Q3TextEdit::normalCopy() +{ +#ifndef QT_NO_MIME + Q3TextDrag *drag = dragObject(); + if (!drag) + return; +#ifndef QT_NO_MIMECLIPBOARD + QApplication::clipboard()->setData(drag, d->clipboard_mode); +#endif // QT_NO_MIMECLIPBOARD +#endif // QT_NO_MIME +} + +/*! + Copies any selected text (from selection 0) to the clipboard. + + \sa hasSelectedText() copyAvailable() +*/ + +void Q3TextEdit::copy() +{ +#ifndef QT_NO_CLIPBOARD +# ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode && optimHasSelection()) + QApplication::clipboard()->setText(optimSelectedText(), d->clipboard_mode); + else + normalCopy(); +# else + normalCopy(); +# endif +#endif +} + +/*! + \internal + + Re-indents the current paragraph. +*/ + +void Q3TextEdit::indent() +{ + if (isReadOnly()) + return; + + drawCursor(false); + if (!doc->hasSelection(Q3TextDocument::Standard)) + cursor->indent(); + else + doc->indentSelection(Q3TextDocument::Standard); + repaintChanged(); + drawCursor(true); + setModified(); + emit textChanged(); +} + +/*! + Reimplemented to allow tabbing through links. If \a n is true the + tab moves the focus to the next child; if \a n is false the tab + moves the focus to the previous child. Returns true if the focus + was moved; otherwise returns false. + */ + +bool Q3TextEdit::focusNextPrevChild(bool n) +{ + if (!isReadOnly() || !linksEnabled()) + return false; + bool b = doc->focusNextPrevChild(n); + repaintChanged(); + if (b) { + Q3TextParagraph *p = doc->focusIndicator.parag; + int start = doc->focusIndicator.start; + int len = doc->focusIndicator.len; + + int y = p->rect().y(); + while (p + && len == 0 + && p->at(start)->isCustom() + && p->at(start)->customItem()->isNested()) { + + Q3TextTable *t = (Q3TextTable*)p->at(start)->customItem(); + QList<Q3TextTableCell *> cells = t->tableCells(); + for (int idx = 0; idx < cells.count(); ++idx) { + Q3TextTableCell *c = cells.at(idx); + Q3TextDocument *cellDoc = c->richText(); + if ( cellDoc->hasFocusParagraph() ) { + y += c->geometry().y() + c->verticalAlignmentOffset(); + + p = cellDoc->focusIndicator.parag; + start = cellDoc->focusIndicator.start; + len = cellDoc->focusIndicator.len; + if ( p ) + y += p->rect().y(); + + break; + } + } + } + setContentsPos( contentsX(), QMIN( y, contentsHeight() - visibleHeight() ) ); + } + return b; +} + +/*! + \internal + + This functions sets the current format to \a f. Only the fields of \a + f which are specified by the \a flags are used. +*/ + +void Q3TextEdit::setFormat(Q3TextFormat *f, int flags) +{ + if (doc->hasSelection(Q3TextDocument::Standard)) { + drawCursor(false); + Q3TextCursor c1 = doc->selectionStartCursor(Q3TextDocument::Standard); + c1.restoreState(); + Q3TextCursor c2 = doc->selectionEndCursor(Q3TextDocument::Standard); + c2.restoreState(); + if (undoEnabled) { + clearUndoRedo(); + undoRedoInfo.type = UndoRedoInfo::Format; + undoRedoInfo.id = c1.paragraph()->paragId(); + undoRedoInfo.index = c1.index(); + undoRedoInfo.eid = c2.paragraph()->paragId(); + undoRedoInfo.eindex = c2.index(); + readFormats(c1, c2, undoRedoInfo.d->text); + undoRedoInfo.format = f; + undoRedoInfo.flags = flags; + clearUndoRedo(); + } + doc->setFormat(Q3TextDocument::Standard, f, flags); + repaintChanged(); + formatMore(); + drawCursor(true); + setModified(); + emit textChanged(); + } + if (currentFormat && currentFormat->key() != f->key()) { + currentFormat->removeRef(); + currentFormat = doc->formatCollection()->format(f); + if (currentFormat->isMisspelled()) { + currentFormat->removeRef(); + currentFormat = doc->formatCollection()->format(currentFormat->font(), + currentFormat->color()); + } + emit currentFontChanged(currentFormat->font()); + emit currentColorChanged(currentFormat->color()); + emit currentVerticalAlignmentChanged((VerticalAlignment)currentFormat->vAlign()); + if (cursor->index() == cursor->paragraph()->length() - 1) { + currentFormat->addRef(); + cursor->paragraph()->string()->setFormat(cursor->index(), currentFormat, true); + if (cursor->paragraph()->length() == 1) { + cursor->paragraph()->invalidate(0); + cursor->paragraph()->format(); + repaintChanged(); + } + } + } +} + +/*! \internal + \warning In Qt 3.1 we will provide a cleaer API for the + functionality which is provided by this function and in Qt 4.0 this + function will go away. + + Sets the paragraph style of the current paragraph + to \a dm. If \a dm is Q3StyleSheetItem::DisplayListItem, the + type of the list item is set to \a listStyle. + + \sa setAlignment() +*/ + +void Q3TextEdit::setParagType(Q3StyleSheetItem::DisplayMode dm, + Q3StyleSheetItem::ListStyle listStyle) +{ + if (isReadOnly()) + return; + + drawCursor(false); + Q3TextParagraph *start = cursor->paragraph(); + Q3TextParagraph *end = start; + if (doc->hasSelection(Q3TextDocument::Standard)) { + start = doc->selectionStartCursor(Q3TextDocument::Standard).topParagraph(); + end = doc->selectionEndCursor(Q3TextDocument::Standard).topParagraph(); + if (end->paragId() < start->paragId()) + return; // do not trust our selections + } + + clearUndoRedo(); + undoRedoInfo.type = UndoRedoInfo::Style; + undoRedoInfo.id = start->paragId(); + undoRedoInfo.eid = end->paragId(); + undoRedoInfo.styleInformation = Q3TextStyleCommand::readStyleInformation(doc, undoRedoInfo.id, undoRedoInfo.eid); + + while (start != end->next()) { + start->setListStyle(listStyle); + if (dm == Q3StyleSheetItem::DisplayListItem) { + start->setListItem(true); + if(start->listDepth() == 0) + start->setListDepth(1); + } else if (start->isListItem()) { + start->setListItem(false); + start->setListDepth(qMax(start->listDepth()-1, 0)); + } + start = start->next(); + } + + clearUndoRedo(); + repaintChanged(); + formatMore(); + drawCursor(true); + setModified(); + emit textChanged(); +} + +/*! + Sets the alignment of the current paragraph to \a a. Valid + alignments are Qt::AlignLeft, Qt::AlignRight, + Qt::AlignJustify and Qt::AlignCenter (which centers + horizontally). +*/ + +void Q3TextEdit::setAlignment(int a) +{ + if (isReadOnly() || block_set_alignment) + return; + + drawCursor(false); + Q3TextParagraph *start = cursor->paragraph(); + Q3TextParagraph *end = start; + if (doc->hasSelection(Q3TextDocument::Standard)) { + start = doc->selectionStartCursor(Q3TextDocument::Standard).topParagraph(); + end = doc->selectionEndCursor(Q3TextDocument::Standard).topParagraph(); + if (end->paragId() < start->paragId()) + return; // do not trust our selections + } + + clearUndoRedo(); + undoRedoInfo.type = UndoRedoInfo::Style; + undoRedoInfo.id = start->paragId(); + undoRedoInfo.eid = end->paragId(); + undoRedoInfo.styleInformation = Q3TextStyleCommand::readStyleInformation(doc, undoRedoInfo.id, undoRedoInfo.eid); + + while (start != end->next()) { + start->setAlignment(a); + start = start->next(); + } + + clearUndoRedo(); + repaintChanged(); + formatMore(); + drawCursor(true); + if (currentAlignment != a) { + currentAlignment = a; + emit currentAlignmentChanged(currentAlignment); + } + setModified(); + emit textChanged(); +} + +void Q3TextEdit::updateCurrentFormat() +{ + int i = cursor->index(); + if (i > 0) + --i; + if (doc->useFormatCollection() && + (!currentFormat || currentFormat->key() != cursor->paragraph()->at(i)->format()->key())) { + if (currentFormat) + currentFormat->removeRef(); + currentFormat = doc->formatCollection()->format(cursor->paragraph()->at(i)->format()); + if (currentFormat->isMisspelled()) { + currentFormat->removeRef(); + currentFormat = doc->formatCollection()->format(currentFormat->font(), currentFormat->color()); + } + emit currentFontChanged(currentFormat->font()); + emit currentColorChanged(currentFormat->color()); + emit currentVerticalAlignmentChanged((VerticalAlignment)currentFormat->vAlign()); + } + + if (currentAlignment != cursor->paragraph()->alignment()) { + currentAlignment = cursor->paragraph()->alignment(); + block_set_alignment = true; + emit currentAlignmentChanged(currentAlignment); + block_set_alignment = false; + } +} + +/*! + If \a b is true sets the current format to italic; otherwise sets + the current format to non-italic. + + \sa italic() +*/ + +void Q3TextEdit::setItalic(bool b) +{ + Q3TextFormat f(*currentFormat); + f.setItalic(b); + Q3TextFormat *f2 = doc->formatCollection()->format(&f); + setFormat(f2, Q3TextFormat::Italic); +} + +/*! + If \a b is true sets the current format to bold; otherwise sets + the current format to non-bold. + + \sa bold() +*/ + +void Q3TextEdit::setBold(bool b) +{ + Q3TextFormat f(*currentFormat); + f.setBold(b); + Q3TextFormat *f2 = doc->formatCollection()->format(&f); + setFormat(f2, Q3TextFormat::Bold); +} + +/*! + If \a b is true sets the current format to underline; otherwise + sets the current format to non-underline. + + \sa underline() +*/ + +void Q3TextEdit::setUnderline(bool b) +{ + Q3TextFormat f(*currentFormat); + f.setUnderline(b); + Q3TextFormat *f2 = doc->formatCollection()->format(&f); + setFormat(f2, Q3TextFormat::Underline); +} + +/*! + Sets the font family of the current format to \a fontFamily. + + \sa family() setCurrentFont() +*/ + +void Q3TextEdit::setFamily(const QString &fontFamily) +{ + Q3TextFormat f(*currentFormat); + f.setFamily(fontFamily); + Q3TextFormat *f2 = doc->formatCollection()->format(&f); + setFormat(f2, Q3TextFormat::Family); +} + +/*! + Sets the point size of the current format to \a s. + + Note that if \a s is zero or negative, the behavior of this + function is not defined. + + \sa pointSize() setCurrentFont() setFamily() +*/ + +void Q3TextEdit::setPointSize(int s) +{ + Q3TextFormat f(*currentFormat); + f.setPointSize(s); + Q3TextFormat *f2 = doc->formatCollection()->format(&f); + setFormat(f2, Q3TextFormat::Size); +} + +/*! + Sets the color of the current format, i.e. of the text, to \a c. + + \sa color() setPaper() +*/ + +void Q3TextEdit::setColor(const QColor &c) +{ + Q3TextFormat f(*currentFormat); + f.setColor(c); + Q3TextFormat *f2 = doc->formatCollection()->format(&f); + setFormat(f2, Q3TextFormat::Color); +} + +/*! + Sets the vertical alignment of the current format, i.e. of the + text, to \a a. + + \sa color() setPaper() +*/ + +void Q3TextEdit::setVerticalAlignment(Q3TextEdit::VerticalAlignment a) +{ + Q3TextFormat f(*currentFormat); + f.setVAlign((Q3TextFormat::VerticalAlignment)a); + Q3TextFormat *f2 = doc->formatCollection()->format(&f); + setFormat(f2, Q3TextFormat::VAlign); +} + +void Q3TextEdit::setFontInternal(const QFont &f_) +{ + QFont font = f_; + if (font.kerning()) + font.setKerning(false); + Q3TextFormat f(*currentFormat); + f.setFont(font); + Q3TextFormat *f2 = doc->formatCollection()->format(&f); + setFormat(f2, Q3TextFormat::Font); +} + + +QString Q3TextEdit::text() const +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) + return optimText(); +#endif + + Q3TextParagraph *p = doc->firstParagraph(); + if (!p || (!p->next() && p->length() <= 1)) + return QString::fromLatin1(""); + + if (isReadOnly()) + return doc->originalText(); + return doc->text(); +} + +/*! + \overload + + Returns the text of paragraph \a para. + + If textFormat() is Qt::RichText the text will contain HTML + formatting tags. +*/ + +QString Q3TextEdit::text(int para) const +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode && (d->od->numLines >= para)) { + QString paraStr = d->od->lines[LOGOFFSET(para)]; + if (paraStr.isEmpty()) + paraStr = QLatin1Char('\n'); + return paraStr; + } else +#endif + return doc->text(para); +} + +/*! + \overload + + Changes the text of the text edit to the string \a text and the + context to \a context. Any previous text is removed. + + \a text may be interpreted either as plain text or as rich text, + depending on the textFormat(). The default setting is Qt::AutoText, + i.e. the text edit auto-detects the format from \a text. + + For rich text the rendering style and available tags are defined + by a styleSheet(); see Q3StyleSheet for details. + + The optional \a context is a path which the text edit's + Q3MimeSourceFactory uses to resolve the locations of files and + images. (See \l{Q3TextEdit::Q3TextEdit()}.) It is passed to the text + edit's Q3MimeSourceFactory when quering data. + + Note that the undo/redo history is cleared by this function. + + \sa text(), setTextFormat() +*/ + +void Q3TextEdit::setText(const QString &text, const QString &context) +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + optimSetText(text); + return; + } +#endif + if (!isModified() && isReadOnly() && + this->context() == context && this->text() == text) + return; + + emit undoAvailable(false); + emit redoAvailable(false); + undoRedoInfo.clear(); + doc->commands()->clear(); + + lastFormatted = 0; + int oldCursorPos = cursor->index(); + int oldCursorPar = cursor->paragraph()->paragId(); + cursor->restoreState(); + delete cursor; + doc->setText(text, context); + + if (wrapMode == FixedPixelWidth) { + resizeContents(wrapWidth, 0); + doc->setWidth(wrapWidth); + doc->setMinimumWidth(wrapWidth); + } else { + doc->setMinimumWidth(-1); + resizeContents(0, 0); + } + + lastFormatted = doc->firstParagraph(); + cursor = new Q3TextCursor(doc); + updateContents(); + + if (isModified()) + setModified(false); + emit textChanged(); + if (cursor->index() != oldCursorPos || cursor->paragraph()->paragId() != oldCursorPar) { + emit cursorPositionChanged(cursor); + emit cursorPositionChanged(cursor->paragraph()->paragId(), cursor->index()); + } + formatMore(); + updateCurrentFormat(); + d->scrollToAnchor.clear(); +} + +/*! + \property Q3TextEdit::text + \brief the text edit's text + + There is no default text. + + On setting, any previous text is deleted. + + The text may be interpreted either as plain text or as rich text, + depending on the textFormat(). The default setting is Qt::AutoText, + i.e. the text edit auto-detects the format of the text. + + For richtext, calling text() on an editable Q3TextEdit will cause + the text to be regenerated from the textedit. This may mean that + the QString returned may not be exactly the same as the one that + was set. + + \sa textFormat +*/ + + +/*! + \property Q3TextEdit::readOnly + \brief whether the text edit is read-only + + In a read-only text edit the user can only navigate through the + text and select text; modifying the text is not possible. + + This property's default is false. +*/ + +/*! + Finds the next occurrence of the string, \a expr. Returns true if + \a expr was found; otherwise returns false. + + If \a para and \a index are both 0 the search begins from the + current cursor position. If \a para and \a index are both not 0, + the search begins from the \c{*}\a{index} character position in the + \c{*}\a{para} paragraph. + + If \a cs is true the search is case sensitive, otherwise it is + case insensitive. If \a wo is true the search looks for whole word + matches only; otherwise it searches for any matching text. If \a + forward is true (the default) the search works forward from the + starting position to the end of the text, otherwise it works + backwards to the beginning of the text. + + If \a expr is found the function returns true. If \a index and \a + para are not 0, the number of the paragraph in which the first + character of the match was found is put into \c{*}\a{para}, and the + index position of that character within the paragraph is put into + \c{*}\a{index}. + + If \a expr is not found the function returns false. If \a index + and \a para are not 0 and \a expr is not found, \c{*}\a{index} + and \c{*}\a{para} are undefined. + + Please note that this function will make the next occurrence of + the string (if found) the current selection, and will thus + modify the cursor position. + + Using the \a para and \a index parameters will not work correctly + in case the document contains tables. +*/ + +bool Q3TextEdit::find(const QString &expr, bool cs, bool wo, bool forward, + int *para, int *index) +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) + return optimFind(expr, cs, wo, forward, para, index); +#endif + drawCursor(false); +#ifndef QT_NO_CURSOR + viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor); +#endif + Q3TextCursor findcur = *cursor; + if (para && index) { + if (doc->paragAt(*para)) + findcur.gotoPosition(doc->paragAt(*para), *index); + else + findcur.gotoEnd(); + } else if (doc->hasSelection(Q3TextDocument::Standard)){ + // maks sure we do not find the same selection again + if (forward) + findcur.gotoNextLetter(); + else + findcur.gotoPreviousLetter(); + } else if (!forward && findcur.index() == 0 && findcur.paragraph() == findcur.topParagraph()) { + findcur.gotoEnd(); + } + removeSelection(Q3TextDocument::Standard); + bool found = doc->find(findcur, expr, cs, wo, forward); + if (found) { + if (para) + *para = findcur.paragraph()->paragId(); + if (index) + *index = findcur.index(); + *cursor = findcur; + repaintChanged(); + ensureCursorVisible(); + } + drawCursor(true); + if (found) { + emit cursorPositionChanged(cursor); + emit cursorPositionChanged(cursor->paragraph()->paragId(), cursor->index()); + } + return found; +} + +void Q3TextEdit::blinkCursor() +{ + bool cv = cursorVisible; + blinkCursorVisible = !blinkCursorVisible; + drawCursor(blinkCursorVisible); + cursorVisible = cv; +} + +/*! + Sets the cursor to position \a index in paragraph \a para. + + \sa getCursorPosition() +*/ + +void Q3TextEdit::setCursorPosition(int para, int index) +{ + Q3TextParagraph *p = doc->paragAt(para); + if (!p) + return; + + if (index > p->length() - 1) + index = p->length() - 1; + + drawCursor(false); + cursor->setParagraph(p); + cursor->setIndex(index); + ensureCursorVisible(); + drawCursor(true); + updateCurrentFormat(); + emit cursorPositionChanged(cursor); + emit cursorPositionChanged(cursor->paragraph()->paragId(), cursor->index()); +} + +/*! + This function sets the \c{*}\a{para} and \c{*}\a{index} parameters to the + current cursor position. \a para and \a index must not be 0. + + \sa setCursorPosition() +*/ + +void Q3TextEdit::getCursorPosition(int *para, int *index) const +{ + if (!para || !index) + return; + *para = cursor->paragraph()->paragId(); + *index = cursor->index(); +} + +/*! + Sets a selection which starts at position \a indexFrom in + paragraph \a paraFrom and ends at position \a indexTo in paragraph + \a paraTo. + + Any existing selections which have a different id (\a selNum) are + left alone, but if an existing selection has the same id as \a + selNum it is removed and replaced by this selection. + + Uses the selection settings of selection \a selNum. If \a selNum + is 0, this is the default selection. + + The cursor is moved to the end of the selection if \a selNum is 0, + otherwise the cursor position remains unchanged. + + \sa getSelection() selectedText +*/ + +void Q3TextEdit::setSelection(int paraFrom, int indexFrom, + int paraTo, int indexTo, int selNum) +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + optimSetSelection(paraFrom, indexFrom, paraTo, indexTo); + repaintContents(); + return; + } +#endif + if (doc->hasSelection(selNum)) { + doc->removeSelection(selNum); + repaintChanged(); + } + if (selNum > doc->numSelections() - 1) + doc->addSelection(selNum); + Q3TextParagraph *p1 = doc->paragAt(paraFrom); + if (!p1) + return; + Q3TextParagraph *p2 = doc->paragAt(paraTo); + if (!p2) + return; + + if (indexFrom > p1->length() - 1) + indexFrom = p1->length() - 1; + if (indexTo > p2->length() - 1) + indexTo = p2->length() - 1; + + drawCursor(false); + Q3TextCursor c = *cursor; + Q3TextCursor oldCursor = *cursor; + c.setParagraph(p1); + c.setIndex(indexFrom); + cursor->setParagraph(p2); + cursor->setIndex(indexTo); + doc->setSelectionStart(selNum, c); + doc->setSelectionEnd(selNum, *cursor); + repaintChanged(); + ensureCursorVisible(); + if (selNum != Q3TextDocument::Standard) + *cursor = oldCursor; + drawCursor(true); +} + +/*! + If there is a selection, \c{*}\a{paraFrom} is set to the number of the + paragraph in which the selection begins and \c{*}\a{paraTo} is set to + the number of the paragraph in which the selection ends. (They + could be the same.) \c{*}\a{indexFrom} is set to the index at which the + selection begins within \c{*}\a{paraFrom}, and \c{*}\a{indexTo} is set to + the index at which the selection ends within \c{*}\a{paraTo}. + + If there is no selection, \c{*}\a{paraFrom}, \c{*}\a{indexFrom}, + \c{*}\a{paraTo} and \c{*}\a{indexTo} are all set to -1. + + If \a paraFrom, \a indexFrom, \a paraTo or \a indexTo is 0 this + function does nothing. + + The \a selNum is the number of the selection (multiple selections + are supported). It defaults to 0 (the default selection). + + \sa setSelection() selectedText +*/ + +void Q3TextEdit::getSelection(int *paraFrom, int *indexFrom, + int *paraTo, int *indexTo, int selNum) const +{ + if (!paraFrom || !paraTo || !indexFrom || !indexTo) + return; +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + *paraFrom = d->od->selStart.line; + *paraTo = d->od->selEnd.line; + *indexFrom = d->od->selStart.index; + *indexTo = d->od->selEnd.index; + return; + } +#endif + if (!doc->hasSelection(selNum)) { + *paraFrom = -1; + *indexFrom = -1; + *paraTo = -1; + *indexTo = -1; + return; + } + + doc->selectionStart(selNum, *paraFrom, *indexFrom); + doc->selectionEnd(selNum, *paraTo, *indexTo); +} + +/*! + \property Q3TextEdit::textFormat + \brief the text format: rich text, plain text, log text or auto text. + + The text format is one of the following: + \list + \i Qt::PlainText - all characters, except newlines, are displayed + verbatim, including spaces. Whenever a newline appears in the text + the text edit inserts a hard line break and begins a new + paragraph. + \i Qt::RichText - rich text rendering. The available styles are + defined in the default stylesheet Q3StyleSheet::defaultSheet(). + \i Qt::LogText - optimized mode for very large texts. Supports a very + limited set of formatting tags (color, bold, underline and italic + settings). + \i Qt::AutoText - this is the default. The text edit autodetects which + rendering style is best, Qt::PlainText or Qt::RichText. This is done + by using the Q3StyleSheet::mightBeRichText() function. + \endlist +*/ + +void Q3TextEdit::setTextFormat(Qt::TextFormat format) +{ + doc->setTextFormat(format); +#ifdef QT_TEXTEDIT_OPTIMIZATION + checkOptimMode(); +#endif +} + +Qt::TextFormat Q3TextEdit::textFormat() const +{ + return doc->textFormat(); +} + +/*! + Returns the number of paragraphs in the text; an empty textedit is always + considered to have one paragraph, so 1 is returned in this case. +*/ + +int Q3TextEdit::paragraphs() const +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + return d->od->numLines; + } +#endif + return doc->lastParagraph()->paragId() + 1; +} + +/*! + Returns the number of lines in paragraph \a para, or -1 if there + is no paragraph with index \a para. +*/ + +int Q3TextEdit::linesOfParagraph(int para) const +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + if (d->od->numLines >= para) + return 1; + else + return -1; + } +#endif + Q3TextParagraph *p = doc->paragAt(para); + if (!p) + return -1; + return p->lines(); +} + +/*! + Returns the length of the paragraph \a para (i.e. the number of + characters), or -1 if there is no paragraph with index \a para. + + This function ignores newlines. +*/ + +int Q3TextEdit::paragraphLength(int para) const +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + if (d->od->numLines >= para) { + if (d->od->lines[LOGOFFSET(para)].isEmpty()) // CR + return 1; + else + return d->od->lines[LOGOFFSET(para)].length(); + } + return -1; + } +#endif + Q3TextParagraph *p = doc->paragAt(para); + if (!p) + return -1; + return p->length() - 1; +} + +/*! + Returns the number of lines in the text edit; this could be 0. + + \warning This function may be slow. Lines change all the time + during word wrapping, so this function has to iterate over all the + paragraphs and get the number of lines from each one individually. +*/ + +int Q3TextEdit::lines() const +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + return d->od->numLines; + } +#endif + Q3TextParagraph *p = doc->firstParagraph(); + int l = 0; + while (p) { + l += p->lines(); + p = p->next(); + } + + return l; +} + +/*! + Returns the line number of the line in paragraph \a para in which + the character at position \a index appears. The \a index position is + relative to the beginning of the paragraph. If there is no such + paragraph or no such character at the \a index position (e.g. the + index is out of range) -1 is returned. +*/ + +int Q3TextEdit::lineOfChar(int para, int index) +{ + Q3TextParagraph *p = doc->paragAt(para); + if (!p) + return -1; + + int idx, line; + Q3TextStringChar *c = p->lineStartOfChar(index, &idx, &line); + if (!c) + return -1; + + return line; +} + +void Q3TextEdit::setModified(bool m) +{ + bool oldModified = modified; + modified = m; + if (modified && doc->oTextValid) + doc->invalidateOriginalText(); + if (oldModified != modified) + emit modificationChanged(modified); +} + +/*! + \property Q3TextEdit::modified + \brief whether the document has been modified by the user +*/ + +bool Q3TextEdit::isModified() const +{ + return modified; +} + +void Q3TextEdit::setModified() +{ + if (!isModified()) + setModified(true); +} + +/*! + Returns true if the current format is italic; otherwise returns false. + + \sa setItalic() +*/ + +bool Q3TextEdit::italic() const +{ + return currentFormat->font().italic(); +} + +/*! + Returns true if the current format is bold; otherwise returns false. + + \sa setBold() +*/ + +bool Q3TextEdit::bold() const +{ + return currentFormat->font().bold(); +} + +/*! + Returns true if the current format is underlined; otherwise returns + false. + + \sa setUnderline() +*/ + +bool Q3TextEdit::underline() const +{ + return currentFormat->font().underline(); +} + +/*! + Returns the font family of the current format. + + \sa setFamily() setCurrentFont() setPointSize() +*/ + +QString Q3TextEdit::family() const +{ + return currentFormat->font().family(); +} + +/*! + Returns the point size of the font of the current format. + + \sa setFamily() setCurrentFont() setPointSize() +*/ + +int Q3TextEdit::pointSize() const +{ + return currentFormat->font().pointSize(); +} + +/*! + Returns the color of the current format. + + \sa setColor() setPaper() +*/ + +QColor Q3TextEdit::color() const +{ + return currentFormat->color(); +} + +/*! + Returns Q3ScrollView::font() + + \warning In previous versions this function returned the font of + the current format. This lead to confusion. Please use + currentFont() instead. +*/ + +QFont Q3TextEdit::font() const +{ + return Q3ScrollView::font(); +} + +/*! + Returns the font of the current format. + + \sa setCurrentFont() setFamily() setPointSize() +*/ + +QFont Q3TextEdit::currentFont() const +{ + return currentFormat->font(); +} + + +/*! + Returns the alignment of the current paragraph. + + \sa setAlignment() +*/ + +int Q3TextEdit::alignment() const +{ + return currentAlignment; +} + +/*! + Returns the vertical alignment of the current format. + + \sa setVerticalAlignment() +*/ + +Q3TextEdit::VerticalAlignment Q3TextEdit::verticalAlignment() const +{ + return (Q3TextEdit::VerticalAlignment) currentFormat->vAlign(); +} + +void Q3TextEdit::startDrag() +{ +#ifndef QT_NO_DRAGANDDROP + mousePressed = false; + inDoubleClick = false; + Q3DragObject *drag = dragObject(viewport()); + if (!drag) + return; + if (isReadOnly()) { + drag->dragCopy(); + } else { + if (drag->drag() && Q3DragObject::target() != this && Q3DragObject::target() != viewport()) + removeSelectedText(); + } +#endif +} + +/*! + If \a select is true (the default), all the text is selected as + selection 0. If \a select is false any selected text is + unselected, i.e. the default selection (selection 0) is cleared. + + \sa selectedText +*/ + +void Q3TextEdit::selectAll(bool select) +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + if (select) + optimSelectAll(); + else + optimRemoveSelection(); + return; + } +#endif + if (!select) + doc->removeSelection(Q3TextDocument::Standard); + else + doc->selectAll(Q3TextDocument::Standard); + repaintChanged(); + emit copyAvailable(doc->hasSelection(Q3TextDocument::Standard)); + emit selectionChanged(); +#ifndef QT_NO_CURSOR + viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor); +#endif +} + +void Q3TextEdit::UndoRedoInfo::clear() +{ + if (valid()) { + if (type == Insert || type == Return) + doc->addCommand(new Q3TextInsertCommand(doc, id, index, d->text.rawData(), styleInformation)); + else if (type == Format) + doc->addCommand(new Q3TextFormatCommand(doc, id, index, eid, eindex, d->text.rawData(), format, flags)); + else if (type == Style) + doc->addCommand(new Q3TextStyleCommand(doc, id, eid, styleInformation)); + else if (type != Invalid) { + doc->addCommand(new Q3TextDeleteCommand(doc, id, index, d->text.rawData(), styleInformation)); + } + } + type = Invalid; + d->text.clear(); + id = -1; + index = -1; + styleInformation = QByteArray(); +} + + +/*! + If there is some selected text (in selection 0) it is deleted. If + there is no selected text (in selection 0) the character to the + right of the text cursor is deleted. + + \sa removeSelectedText() cut() +*/ + +void Q3TextEdit::del() +{ + if (doc->hasSelection(Q3TextDocument::Standard)) { + removeSelectedText(); + return; + } + + doKeyboardAction(ActionDelete); +} + + +Q3TextEdit::UndoRedoInfo::UndoRedoInfo(Q3TextDocument *dc) + : type(Invalid), doc(dc) +{ + d = new QUndoRedoInfoPrivate; + d->text.clear(); + id = -1; + index = -1; +} + +Q3TextEdit::UndoRedoInfo::~UndoRedoInfo() +{ + delete d; +} + +bool Q3TextEdit::UndoRedoInfo::valid() const +{ + return id >= 0 && type != Invalid; +} + +/*! + \internal + + Resets the current format to the default format. +*/ + +void Q3TextEdit::resetFormat() +{ + setAlignment(Qt::AlignAuto); + setParagType(Q3StyleSheetItem::DisplayBlock, Q3StyleSheetItem::ListDisc); + setFormat(doc->formatCollection()->defaultFormat(), Q3TextFormat::Format); +} + +/*! + Returns the Q3StyleSheet which is being used by this text edit. + + \sa setStyleSheet() +*/ + +Q3StyleSheet* Q3TextEdit::styleSheet() const +{ + return doc->styleSheet(); +} + +/*! + Sets the stylesheet to use with this text edit to \a styleSheet. + Changes will only take effect for new text added with setText() or + append(). + + \sa styleSheet() +*/ + +void Q3TextEdit::setStyleSheet(Q3StyleSheet* styleSheet) +{ + doc->setStyleSheet(styleSheet); +} + +/*! + \property Q3TextEdit::paper + \brief the background (paper) brush. + + The brush that is currently used to draw the background of the + text edit. The initial setting is an empty brush. +*/ + +void Q3TextEdit::setPaper(const QBrush& pap) +{ + doc->setPaper(new QBrush(pap)); + if ( pap.pixmap() ) + viewport()->setBackgroundPixmap( *pap.pixmap() ); + QPalette pal = palette(); + pal.setColor(QPalette::Window, pap.color()); + setPalette(pal); + pal = viewport()->palette(); + pal.setColor(QPalette::Window, pap.color()); + viewport()->setPalette(pal); +#ifdef QT_TEXTEDIT_OPTIMIZATION + // force a repaint of the entire viewport - using updateContents() + // would clip the coords to the content size + if (d->optimMode) + repaintContents(contentsX(), contentsY(), viewport()->width(), viewport()->height()); + else +#endif + updateContents(); +} + +QBrush Q3TextEdit::paper() const +{ + if (doc->paper()) + return *doc->paper(); + return QBrush(palette().base()); +} + +/*! + \property Q3TextEdit::linkUnderline + \brief whether hypertext links will be underlined + + If true (the default) hypertext links will be displayed + underlined. If false links will not be displayed underlined. +*/ + +void Q3TextEdit::setLinkUnderline(bool b) +{ + if (doc->underlineLinks() == b) + return; + doc->setUnderlineLinks(b); + repaintChanged(); +} + +bool Q3TextEdit::linkUnderline() const +{ + return doc->underlineLinks(); +} + +/*! + Sets the text edit's mimesource factory to \a factory. See + Q3MimeSourceFactory for further details. + + \sa mimeSourceFactory() + */ + +#ifndef QT_NO_MIME +void Q3TextEdit::setMimeSourceFactory(Q3MimeSourceFactory* factory) +{ + doc->setMimeSourceFactory(factory); +} + +/*! + Returns the Q3MimeSourceFactory which is being used by this text + edit. + + \sa setMimeSourceFactory() +*/ + +Q3MimeSourceFactory* Q3TextEdit::mimeSourceFactory() const +{ + return doc->mimeSourceFactory(); +} +#endif + +/*! + Returns how many pixels high the text edit needs to be to display + all the text if the text edit is \a w pixels wide. +*/ + +int Q3TextEdit::heightForWidth(int w) const +{ + int oldw = doc->width(); + doc->doLayout(0, w); + int h = doc->height(); + doc->setWidth(oldw); + doc->invalidate(); + ((Q3TextEdit*)this)->formatMore(); + return h; +} + +/*! + Appends a new paragraph with \a text to the end of the text edit. Note that + the undo/redo history is cleared by this function, and no undo + history is kept for appends which makes them faster than + insert()s. If you want to append text which is added to the + undo/redo history as well, use insertParagraph(). +*/ + +void Q3TextEdit::append(const QString &text) +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + optimAppend(text); + return; + } +#endif + // flush and clear the undo/redo stack if necessary + undoRedoInfo.clear(); + doc->commands()->clear(); + + doc->removeSelection(Q3TextDocument::Standard); + Qt::TextFormat f = doc->textFormat(); + if (f == Qt::AutoText) { + if (Q3StyleSheet::mightBeRichText(text)) + f = Qt::RichText; + else + f = Qt::PlainText; + } + + drawCursor(false); + Q3TextCursor oldc(*cursor); + ensureFormatted(doc->lastParagraph()); + bool atBottom = contentsY() >= contentsHeight() - visibleHeight(); + cursor->gotoEnd(); + if (cursor->index() > 0) + cursor->splitAndInsertEmptyParagraph(); + Q3TextCursor oldCursor2 = *cursor; + + if (f == Qt::PlainText) { + cursor->insert(text, true); + if (doc->useFormatCollection() && !doc->preProcessor() && + currentFormat != cursor->paragraph()->at( cursor->index() )->format()) { + doc->setSelectionStart( Q3TextDocument::Temp, oldCursor2 ); + doc->setSelectionEnd( Q3TextDocument::Temp, *cursor ); + doc->setFormat( Q3TextDocument::Temp, currentFormat, Q3TextFormat::Format ); + doc->removeSelection( Q3TextDocument::Temp ); + } + } else { + cursor->paragraph()->setListItem(false); + cursor->paragraph()->setListDepth(0); + if (cursor->paragraph()->prev()) + cursor->paragraph()->prev()->invalidate(0); // vertical margins might have to change + doc->setRichTextInternal(text); + } + formatMore(); + repaintChanged(); + if (atBottom) + scrollToBottom(); + *cursor = oldc; + if (!isReadOnly()) + cursorVisible = true; + setModified(); + emit textChanged(); +} + +/*! + \property Q3TextEdit::hasSelectedText + \brief whether some text is selected in selection 0 +*/ + +bool Q3TextEdit::hasSelectedText() const +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) + return optimHasSelection(); + else +#endif + return doc->hasSelection(Q3TextDocument::Standard); +} + +/*! + \property Q3TextEdit::selectedText + \brief The selected text (from selection 0) or an empty string if + there is no currently selected text (in selection 0). + + The text is always returned as Qt::PlainText if the textFormat() is + Qt::PlainText or Qt::AutoText, otherwise it is returned as HTML. + + \sa hasSelectedText +*/ + +QString Q3TextEdit::selectedText() const +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) + return optimSelectedText(); + else +#endif + return doc->selectedText(Q3TextDocument::Standard, textFormat() == Qt::RichText); +} + +bool Q3TextEdit::handleReadOnlyKeyEvent(QKeyEvent *e) +{ + switch(e->key()) { + case Qt::Key_Down: + setContentsPos(contentsX(), contentsY() + 10); + break; + case Qt::Key_Up: + setContentsPos(contentsX(), contentsY() - 10); + break; + case Qt::Key_Left: + setContentsPos(contentsX() - 10, contentsY()); + break; + case Qt::Key_Right: + setContentsPos(contentsX() + 10, contentsY()); + break; + case Qt::Key_PageUp: + setContentsPos(contentsX(), contentsY() - visibleHeight()); + break; + case Qt::Key_PageDown: + setContentsPos(contentsX(), contentsY() + visibleHeight()); + break; + case Qt::Key_Home: + setContentsPos(contentsX(), 0); + break; + case Qt::Key_End: + setContentsPos(contentsX(), contentsHeight() - visibleHeight()); + break; + case Qt::Key_F16: // Copy key on Sun keyboards + copy(); + break; +#ifndef QT_NO_NETWORKPROTOCOL + case Qt::Key_Return: + case Qt::Key_Enter: + case Qt::Key_Space: { + if (!doc->focusIndicator.href.isEmpty() + || !doc->focusIndicator.name.isEmpty()) { + if (!doc->focusIndicator.href.isEmpty()) { + QUrl u = QUrl(doc->context()).resolved(doc->focusIndicator.href); + emitLinkClicked(u.toString(QUrl::None)); + } + if (!doc->focusIndicator.name.isEmpty()) + if (Q3TextBrowser *browser = qobject_cast<Q3TextBrowser*>(this)) + emit browser->anchorClicked(doc->focusIndicator.name, doc->focusIndicator.href); + +#ifndef QT_NO_CURSOR + viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor); +#endif + } + } break; +#endif + default: + if (e->state() & Qt::ControlButton) { + switch (e->key()) { + case Qt::Key_C: case Qt::Key_F16: // Copy key on Sun keyboards + copy(); + break; +#ifdef Q_WS_WIN + case Qt::Key_Insert: + copy(); + break; + case Qt::Key_A: + selectAll(); + break; +#endif + } + + } + return false; + } + return true; +} + +/*! + Returns the context of the text edit. The context is a path which + the text edit's Q3MimeSourceFactory uses to resolve the locations + of files and images. + + \sa text +*/ + +QString Q3TextEdit::context() const +{ + return doc->context(); +} + +/*! + \property Q3TextEdit::documentTitle + \brief the title of the document parsed from the text. + + For Qt::PlainText the title will be an empty string. For \c + Qt::RichText the title will be the text between the \c{<title>} tags, + if present, otherwise an empty string. +*/ + +QString Q3TextEdit::documentTitle() const +{ + return doc->attributes()[QLatin1String("title")]; +} + +void Q3TextEdit::makeParagVisible(Q3TextParagraph *p) +{ + setContentsPos(contentsX(), qMin(p->rect().y(), contentsHeight() - visibleHeight())); +} + +/*! + Scrolls the text edit to make the text at the anchor called \a + name visible, if it can be found in the document. If the anchor + isn't found no scrolling will occur. An anchor is defined using + the HTML anchor tag, e.g. \c{<a name="target">}. +*/ + +void Q3TextEdit::scrollToAnchor(const QString& name) +{ + if (!isVisible()) { + d->scrollToAnchor = name; + return; + } + if (name.isEmpty()) + return; + sync(); + Q3TextCursor cursor(doc); + Q3TextParagraph* last = doc->lastParagraph(); + for (;;) { + Q3TextStringChar* c = cursor.paragraph()->at(cursor.index()); + if(c->isAnchor()) { + QString a = c->anchorName(); + if (a == name || + (a.contains(QLatin1Char('#')) && a.split(QLatin1Char('#')).contains(name))) { + setContentsPos(contentsX(), qMin(cursor.paragraph()->rect().top() + cursor.totalOffsetY(), contentsHeight() - visibleHeight())); + break; + } + } + if (cursor.paragraph() == last && cursor.atParagEnd() ) + break; + cursor.gotoNextLetter(); + } +} + +/*! + Returns the text for the attribute \a attr (Qt::AnchorHref by + default) if there is an anchor at position \a pos (in contents + coordinates); otherwise returns an empty string. +*/ + +QString Q3TextEdit::anchorAt(const QPoint& pos, Qt::AnchorAttribute attr) +{ + Q3TextCursor c(doc); + placeCursor(pos, &c, true); + switch(attr) { + case Qt::AnchorName: + return c.paragraph()->at(c.index())->anchorName(); + case Qt::AnchorHref: + return c.paragraph()->at(c.index())->anchorHref(); + } + // incase the compiler is really dumb about determining if a function + // returns something :) + return QString(); +} + +void Q3TextEdit::documentWidthChanged(int w) +{ + resizeContents(qMax(visibleWidth(), w), contentsHeight()); +} + +/*! \internal + + This function does nothing +*/ + +void Q3TextEdit::updateStyles() +{ +} + +void Q3TextEdit::setDocument(Q3TextDocument *dc) +{ + if (dc == 0) { + qWarning("Q3TextEdit::setDocument() called with null Q3TextDocument pointer"); + return; + } + if (dc == doc) + return; + doc = dc; + delete cursor; + cursor = new Q3TextCursor(doc); + clearUndoRedo(); + undoRedoInfo.doc = doc; + lastFormatted = 0; +} + +#ifndef QT_NO_CLIPBOARD + +/*! + Pastes the text with format \a subtype from the clipboard into the + text edit at the current cursor position. The \a subtype can be + "plain" or "html". + + If there is no text with format \a subtype in the clipboard + nothing happens. + + \sa paste() cut() Q3TextEdit::copy() +*/ + +void Q3TextEdit::pasteSubType(const QByteArray &subtype) +{ +#ifndef QT_NO_MIMECLIPBOARD + QMimeSource *m = QApplication::clipboard()->data(d->clipboard_mode); + pasteSubType(subtype, m); +#endif +} + +/*! \internal */ + +void Q3TextEdit::pasteSubType(const QByteArray& subtype, QMimeSource *m) +{ +#ifndef QT_NO_MIME + QByteArray st = subtype; + + if (subtype != "x-qrichtext") + st.prepend("text/"); + else + st.prepend("application/"); + if (!m) + return; + if (doc->hasSelection(Q3TextDocument::Standard)) + removeSelectedText(); + if (!Q3RichTextDrag::canDecode(m)) + return; + QString t; + if (!Q3RichTextDrag::decode(m, t, QString::fromLatin1(st), QString::fromLatin1(subtype))) + return; + if (st == "application/x-qrichtext") { + int start; + if ((start = t.indexOf(QLatin1String("<!--StartFragment-->"))) != -1) { + start += 20; + int end = t.indexOf(QLatin1String("<!--EndFragment-->")); + Q3TextCursor oldC = *cursor; + + // during the setRichTextInternal() call the cursors + // paragraph might get joined with the provious one, so + // the cursors one would get deleted and oldC.paragraph() + // would be a dnagling pointer. To avoid that try to go + // one letter back and later go one forward again. + oldC.gotoPreviousLetter(); + bool couldGoBack = oldC != *cursor; + // first para might get deleted, so remember to reset it + bool wasAtFirst = oldC.paragraph() == doc->firstParagraph(); + + if (start < end) + t = t.mid(start, end - start); + else + t = t.mid(start); + lastFormatted = cursor->paragraph(); + if (lastFormatted->prev()) + lastFormatted = lastFormatted->prev(); + doc->setRichTextInternal(t, cursor); + + // the first para might have been deleted in + // setRichTextInternal(). To be sure, reset it if + // necessary. + if (wasAtFirst) { + int index = oldC.index(); + oldC.setParagraph(doc->firstParagraph()); + oldC.setIndex(index); + } + + // if we went back one letter before (see last comment), + // go one forward to point to the right position + if (couldGoBack) + oldC.gotoNextLetter(); + + if (undoEnabled && !isReadOnly()) { + doc->setSelectionStart(Q3TextDocument::Temp, oldC); + doc->setSelectionEnd(Q3TextDocument::Temp, *cursor); + + checkUndoRedoInfo(UndoRedoInfo::Insert); + if (!undoRedoInfo.valid()) { + undoRedoInfo.id = oldC.paragraph()->paragId(); + undoRedoInfo.index = oldC.index(); + undoRedoInfo.d->text.clear(); + } + int oldLen = undoRedoInfo.d->text.length(); + if (!doc->preProcessor()) { + QString txt = doc->selectedText(Q3TextDocument::Temp); + undoRedoInfo.d->text += txt; + for (int i = 0; i < (int)txt.length(); ++i) { + if (txt[i] != QLatin1Char('\n') && oldC.paragraph()->at(oldC.index())->format()) { + oldC.paragraph()->at(oldC.index())->format()->addRef(); + undoRedoInfo.d->text. + setFormat(oldLen + i, oldC.paragraph()->at(oldC.index())->format(), true); + } + oldC.gotoNextLetter(); + } + } + undoRedoInfo.clear(); + removeSelection(Q3TextDocument::Temp); + } + + formatMore(); + setModified(); + emit textChanged(); + repaintChanged(); + ensureCursorVisible(); + return; + } + } else { +#if defined(Q_OS_WIN32) + // Need to convert CRLF to LF + t.replace(QLatin1String("\r\n"), QLatin1String("\n")); +#elif defined(Q_OS_MAC) + //need to convert CR to LF + t.replace(QLatin1Char('\r'), QLatin1Char('\n')); +#endif + QChar *uc = (QChar *)t.unicode(); + for (int i = 0; i < t.length(); i++) { + if (uc[i] < QLatin1Char(' ') && uc[i] != QLatin1Char('\n') && uc[i] != QLatin1Char('\t')) + uc[i] = QLatin1Char(' '); + } + if (!t.isEmpty()) + insert(t, false, true); + } +#endif //QT_NO_MIME +} + +#ifndef QT_NO_MIMECLIPBOARD +/*! + Prompts the user to choose a type from a list of text types + available, then copies text from the clipboard (if there is any) + into the text edit at the current text cursor position. Any + selected text (in selection 0) is first deleted. +*/ +void Q3TextEdit::pasteSpecial(const QPoint& pt) +{ + QByteArray st = pickSpecial(QApplication::clipboard()->data(d->clipboard_mode), + true, pt); + if (!st.isEmpty()) + pasteSubType(st); +} +#endif +#ifndef QT_NO_MIME +QByteArray Q3TextEdit::pickSpecial(QMimeSource* ms, bool always_ask, const QPoint& pt) +{ + if (ms) { +#ifndef QT_NO_MENU + QMenu popup(this); + QString fmt; + int n = 0; + QHash<QString, bool> done; + for (int i = 0; !(fmt = QLatin1String(ms->format(i))).isNull(); i++) { + int semi = fmt.indexOf(QLatin1Char(';')); + if (semi >= 0) + fmt = fmt.left(semi); + if (fmt.left(5) == QLatin1String("text/")) { + fmt = fmt.mid(5); + if (!done.contains(fmt)) { + done.insert(fmt,true); + popup.insertItem(fmt, i); + n++; + } + } + } + if (n) { + QAction *action = (n == 1 && !always_ask) + ? popup.actions().at(0) + : popup.exec(pt); + if (action) + return action->text().toLatin1(); + } +#else + QString fmt; + for (int i = 0; !(fmt = ms->format(i)).isNull(); i++) { + int semi = fmt.indexOf(';'); + if (semi >= 0) + fmt = fmt.left(semi); + if (fmt.left(5) == "text/") { + fmt = fmt.mid(5); + return fmt.latin1(); + } + } +#endif + } + return QByteArray(); +} +#endif // QT_NO_MIME +#endif // QT_NO_CLIPBOARD + +/*! + \enum Q3TextEdit::WordWrap + + This enum defines the Q3TextEdit's word wrap modes. + + \value NoWrap Do not wrap the text. + + \value WidgetWidth Wrap the text at the current width of the + widget (this is the default). Wrapping is at whitespace by + default; this can be changed with setWrapPolicy(). + + \value FixedPixelWidth Wrap the text at a fixed number of pixels + from the widget's left side. The number of pixels is set with + wrapColumnOrWidth(). + + \value FixedColumnWidth Wrap the text at a fixed number of + character columns from the widget's left side. The number of + characters is set with wrapColumnOrWidth(). This is useful if you + need formatted text that can also be displayed gracefully on + devices with monospaced fonts, for example a standard VT100 + terminal, where you might set wrapColumnOrWidth() to 80. + + \sa setWordWrap() wordWrap() +*/ + +/*! + \property Q3TextEdit::wordWrap + \brief the word wrap mode + + The default mode is \c WidgetWidth which causes words to be + wrapped at the right edge of the text edit. Wrapping occurs at + whitespace, keeping whole words intact. If you want wrapping to + occur within words use setWrapPolicy(). If you set a wrap mode of + \c FixedPixelWidth or \c FixedColumnWidth you should also call + setWrapColumnOrWidth() with the width you want. + + \sa WordWrap, wrapColumnOrWidth, wrapPolicy, +*/ + +void Q3TextEdit::setWordWrap(WordWrap mode) +{ + if (wrapMode == mode) + return; + wrapMode = mode; + switch (mode) { + case NoWrap: + document()->formatter()->setWrapEnabled(false); + document()->formatter()->setWrapAtColumn(-1); + doc->setWidth(visibleWidth()); + doc->setMinimumWidth(-1); + doc->invalidate(); + updateContents(); + lastFormatted = doc->firstParagraph(); + interval = 0; + formatMore(); + break; + case WidgetWidth: + document()->formatter()->setWrapEnabled(true); + document()->formatter()->setWrapAtColumn(-1); + doResize(); + break; + case FixedPixelWidth: + document()->formatter()->setWrapEnabled(true); + document()->formatter()->setWrapAtColumn(-1); + if (wrapWidth < 0) + wrapWidth = 200; + setWrapColumnOrWidth(wrapWidth); + break; + case FixedColumnWidth: + if (wrapWidth < 0) + wrapWidth = 80; + document()->formatter()->setWrapEnabled(true); + document()->formatter()->setWrapAtColumn(wrapWidth); + setWrapColumnOrWidth(wrapWidth); + break; + } +#ifdef QT_TEXTEDIT_OPTIMIZATION + checkOptimMode(); +#endif +} + +Q3TextEdit::WordWrap Q3TextEdit::wordWrap() const +{ + return wrapMode; +} + +/*! + \property Q3TextEdit::wrapColumnOrWidth + \brief the position (in pixels or columns depending on the wrap mode) where text will be wrapped + + If the wrap mode is \c FixedPixelWidth, the value is the number of + pixels from the left edge of the text edit at which text should be + wrapped. If the wrap mode is \c FixedColumnWidth, the value is the + column number (in character columns) from the left edge of the + text edit at which text should be wrapped. + + \sa wordWrap +*/ +void Q3TextEdit::setWrapColumnOrWidth(int value) +{ + wrapWidth = value; + if (wrapMode == FixedColumnWidth) { + document()->formatter()->setWrapAtColumn(wrapWidth); + resizeContents(0, 0); + doc->setWidth(visibleWidth()); + doc->setMinimumWidth(-1); + } else if (wrapMode == FixedPixelWidth) { + document()->formatter()->setWrapAtColumn(-1); + resizeContents(wrapWidth, 0); + doc->setWidth(wrapWidth); + doc->setMinimumWidth(wrapWidth); + } else { + return; + } + doc->invalidate(); + updateContents(); + lastFormatted = doc->firstParagraph(); + interval = 0; + formatMore(); +} + +int Q3TextEdit::wrapColumnOrWidth() const +{ + if (wrapMode == WidgetWidth) + return visibleWidth(); + return wrapWidth; +} + + +/*! + \enum Q3TextEdit::WrapPolicy + + This enum defines where text can be wrapped in word wrap mode. + + \value AtWhiteSpace Don't use this deprecated value (it is a + synonym for \c AtWordBoundary which you should use instead). + \value Anywhere Break anywhere, including within words. + \value AtWordBoundary Break lines at word boundaries, e.g. spaces or + newlines + \value AtWordOrDocumentBoundary Break lines at whitespace, e.g. + spaces or newlines if possible. Break it anywhere otherwise. + + \sa setWrapPolicy() +*/ + +/*! + \property Q3TextEdit::wrapPolicy + \brief the word wrap policy, at whitespace or anywhere + + Defines where text can be wrapped when word wrap mode is not \c + NoWrap. The choices are \c AtWordBoundary (the default), \c + Anywhere and \c AtWordOrDocumentBoundary + + \sa wordWrap +*/ + +void Q3TextEdit::setWrapPolicy(WrapPolicy policy) +{ + if (wPolicy == policy) + return; + wPolicy = policy; + Q3TextFormatter *formatter; + if (policy == AtWordBoundary || policy == AtWordOrDocumentBoundary) { + formatter = new Q3TextFormatterBreakWords; + formatter->setAllowBreakInWords(policy == AtWordOrDocumentBoundary); + } else { + formatter = new Q3TextFormatterBreakInWords; + } + formatter->setWrapAtColumn(document()->formatter()->wrapAtColumn()); + formatter->setWrapEnabled(document()->formatter()->isWrapEnabled(0)); + document()->setFormatter(formatter); + doc->invalidate(); + updateContents(); + lastFormatted = doc->firstParagraph(); + interval = 0; + formatMore(); +} + +Q3TextEdit::WrapPolicy Q3TextEdit::wrapPolicy() const +{ + return wPolicy; +} + +/*! + Deletes all the text in the text edit. + + \sa cut() removeSelectedText() setText() +*/ + +void Q3TextEdit::clear() +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + optimSetText(QLatin1String("")); + } else +#endif + { + // make clear undoable + doc->selectAll(Q3TextDocument::Temp); + removeSelectedText(Q3TextDocument::Temp); + setContentsPos(0, 0); + if (cursor->isValid()) + cursor->restoreState(); + doc->clear(true); + delete cursor; + cursor = new Q3TextCursor(doc); + lastFormatted = 0; + } + updateContents(); + + emit cursorPositionChanged(cursor); + emit cursorPositionChanged(cursor->paragraph()->paragId(), cursor->index()); +} + +int Q3TextEdit::undoDepth() const +{ + return document()->undoDepth(); +} + +/*! + \property Q3TextEdit::length + \brief the number of characters in the text +*/ + +int Q3TextEdit::length() const +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) + return d->od->len; + else +#endif + return document()->length(); +} + +/*! + \property Q3TextEdit::tabStopWidth + \brief the tab stop width in pixels +*/ + +int Q3TextEdit::tabStopWidth() const +{ + return document()->tabStopWidth(); +} + +void Q3TextEdit::setUndoDepth(int d) +{ + document()->setUndoDepth(d); +} + +void Q3TextEdit::setTabStopWidth(int ts) +{ + document()->setTabStops(ts); + doc->invalidate(); + lastFormatted = doc->firstParagraph(); + interval = 0; + formatMore(); + updateContents(); +} + +/*! + \reimp +*/ + +QSize Q3TextEdit::sizeHint() const +{ + // cf. Q3ScrollView::sizeHint() + ensurePolished(); + int f = 2 * frameWidth(); + int h = fontMetrics().height(); + QSize sz(f, f); + return sz.expandedTo(QSize(12 * h, 8 * h)); +} + +void Q3TextEdit::clearUndoRedo() +{ + if (!undoEnabled) + return; + undoRedoInfo.clear(); + emit undoAvailable(doc->commands()->isUndoAvailable()); + emit redoAvailable(doc->commands()->isRedoAvailable()); +} + +/*! \internal + \warning In Qt 3.1 we will provide a cleaer API for the + functionality which is provided by this function and in Qt 4.0 this + function will go away. + + This function gets the format of the character at position \a + index in paragraph \a para. Sets \a font to the character's font, \a + color to the character's color and \a verticalAlignment to the + character's vertical alignment. + + Returns false if \a para or \a index is out of range otherwise + returns true. +*/ + +bool Q3TextEdit::getFormat(int para, int index, QFont *font, QColor *color, VerticalAlignment *verticalAlignment) +{ + if (!font || !color) + return false; + Q3TextParagraph *p = doc->paragAt(para); + if (!p) + return false; + if (index < 0 || index >= p->length()) + return false; + *font = p->at(index)->format()->font(); + *color = p->at(index)->format()->color(); + *verticalAlignment = (VerticalAlignment)p->at(index)->format()->vAlign(); + return true; +} + +/*! \internal + \warning In Qt 3.1 we will provide a cleaer API for the + functionality which is provided by this function and in Qt 4.0 this + function will go away. + + This function gets the format of the paragraph \a para. Sets \a + font to the paragraphs's font, \a color to the paragraph's color, \a + verticalAlignment to the paragraph's vertical alignment, \a + alignment to the paragraph's alignment, \a displayMode to the + paragraph's display mode, \a listStyle to the paragraph's list style + (if the display mode is Q3StyleSheetItem::DisplayListItem) and \a + listDepth to the depth of the list (if the display mode is + Q3StyleSheetItem::DisplayListItem). + + Returns false if \a para is out of range otherwise returns true. +*/ + +bool Q3TextEdit::getParagraphFormat(int para, QFont *font, QColor *color, + VerticalAlignment *verticalAlignment, int *alignment, + Q3StyleSheetItem::DisplayMode *displayMode, + Q3StyleSheetItem::ListStyle *listStyle, + int *listDepth) +{ + if (!font || !color || !alignment || !displayMode || !listStyle) + return false; + Q3TextParagraph *p = doc->paragAt(para); + if (!p) + return false; + *font = p->at(0)->format()->font(); + *color = p->at(0)->format()->color(); + *verticalAlignment = (VerticalAlignment)p->at(0)->format()->vAlign(); + *alignment = p->alignment(); + *displayMode = p->isListItem() ? Q3StyleSheetItem::DisplayListItem : Q3StyleSheetItem::DisplayBlock; + *listStyle = p->listStyle(); + *listDepth = p->listDepth(); + return true; +} + + + +/*! + This function is called to create a right mouse button popup menu + at the document position \a pos. If you want to create a custom + popup menu, reimplement this function and return the created popup + menu. Ownership of the popup menu is transferred to the caller. + + \warning The QPopupMenu ID values 0-7 are reserved, and they map to the + standard operations. When inserting items into your custom popup menu, be + sure to specify ID values larger than 7. +*/ + +Q3PopupMenu *Q3TextEdit::createPopupMenu(const QPoint& pos) +{ + Q_UNUSED(pos) +#ifndef QT_NO_POPUPMENU + Q3PopupMenu *popup = new Q3PopupMenu(this, "qt_edit_menu"); + if (!isReadOnly()) { + d->id[IdUndo] = popup->insertItem(tr("&Undo") + ACCEL_KEY(Z)); + d->id[IdRedo] = popup->insertItem(tr("&Redo") + ACCEL_KEY(Y)); + popup->addSeparator(); + } +#ifndef QT_NO_CLIPBOARD + if (!isReadOnly()) + d->id[IdCut] = popup->insertItem(tr("Cu&t") + ACCEL_KEY(X)); + d->id[IdCopy] = popup->insertItem(tr("&Copy") + ACCEL_KEY(C)); + if (!isReadOnly()) + d->id[IdPaste] = popup->insertItem(tr("&Paste") + ACCEL_KEY(V)); +#endif + if (!isReadOnly()) { + d->id[IdClear] = popup->insertItem(tr("Clear")); + popup->addSeparator(); + } +#if defined(Q_WS_X11) + d->id[IdSelectAll] = popup->insertItem(tr("Select All")); +#else + d->id[IdSelectAll] = popup->insertItem(tr("Select All") + ACCEL_KEY(A)); +#endif + popup->setItemEnabled(d->id[IdUndo], !isReadOnly() && doc->commands()->isUndoAvailable()); + popup->setItemEnabled(d->id[IdRedo], !isReadOnly() && doc->commands()->isRedoAvailable()); +#ifndef QT_NO_CLIPBOARD + popup->setItemEnabled(d->id[IdCut], !isReadOnly() && doc->hasSelection(Q3TextDocument::Standard, true)); +#ifdef QT_TEXTEDIT_OPTIMIZATION + popup->setItemEnabled(d->id[IdCopy], d->optimMode ? optimHasSelection() : doc->hasSelection(Q3TextDocument::Standard, true)); +#else + popup->setItemEnabled(d->id[IdCopy], doc->hasSelection(Q3TextDocument::Standard, true)); +#endif + popup->setItemEnabled(d->id[IdPaste], !isReadOnly() && !QApplication::clipboard()->text(d->clipboard_mode).isEmpty()); +#endif + const bool isEmptyDocument = (length() == 0); + popup->setItemEnabled(d->id[IdClear], !isReadOnly() && !isEmptyDocument); + popup->setItemEnabled(d->id[IdSelectAll], !isEmptyDocument); + return popup; +#else + return 0; +#endif +} + +/*! \overload + This function is called to create a right mouse button popup menu. + If you want to create a custom popup menu, reimplement this function + and return the created popup menu. Ownership of the popup menu is + transferred to the caller. + + This function is only called if createPopupMenu(const QPoint &) + returns 0. +*/ + +Q3PopupMenu *Q3TextEdit::createPopupMenu() +{ + return 0; +} + +/*! + \fn Q3TextEdit::zoomIn() + + \overload + + Zooms in on the text by making the base font size one point + larger and recalculating all font sizes to be the new size. This + does not change the size of any images. + + \sa zoomOut() +*/ + +/*! + \fn Q3TextEdit::zoomOut() + + \overload + + Zooms out on the text by making the base font size one point + smaller and recalculating all font sizes to be the new size. This + does not change the size of any images. + + \sa zoomIn() +*/ + + +/*! + Zooms in on the text by making the base font size \a range + points larger and recalculating all font sizes to be the new size. + This does not change the size of any images. + + \sa zoomOut() +*/ + +void Q3TextEdit::zoomIn(int range) +{ + QFont f(Q3ScrollView::font()); + f.setPointSize(f.pointSize() + range); + setFont(f); +} + +/*! + Zooms out on the text by making the base font size \a range points + smaller and recalculating all font sizes to be the new size. This + does not change the size of any images. + + \sa zoomIn() +*/ + +void Q3TextEdit::zoomOut(int range) +{ + QFont f(Q3ScrollView::font()); + f.setPointSize(qMax(1, f.pointSize() - range)); + setFont(f); +} + +/*! + Zooms the text by making the base font size \a size points and + recalculating all font sizes to be the new size. This does not + change the size of any images. +*/ + +void Q3TextEdit::zoomTo(int size) +{ + QFont f(Q3ScrollView::font()); + f.setPointSize(size); + setFont(f); +} + +/*! + Q3TextEdit is optimized for large amounts text. One of its + optimizations is to format only the visible text, formatting the rest + on demand, e.g. as the user scrolls, so you don't usually need to + call this function. + + In some situations you may want to force the whole text + to be formatted. For example, if after calling setText(), you wanted + to know the height of the document (using contentsHeight()), you + would call this function first. +*/ + +void Q3TextEdit::sync() +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + QFontMetrics fm(Q3ScrollView::font()); + resizeContents(d->od->maxLineWidth + 4, d->od->numLines * fm.lineSpacing() + 1); + } else +#endif + { + while (lastFormatted) { + lastFormatted->format(); + lastFormatted = lastFormatted->next(); + } + resizeContents(contentsWidth(), doc->height()); + } + updateScrollBars(); +} + +/*! + Sets the background color of selection number \a selNum to \a back + and specifies whether the text of this selection should be + inverted with \a invertText. + + This only works for \a selNum > 0. The default selection (\a + selNum == 0) gets its attributes from the text edit's + palette(). +*/ + +void Q3TextEdit::setSelectionAttributes(int selNum, const QColor &back, bool invertText) +{ + if (selNum < 1) + return; + if (selNum > doc->numSelections()) + doc->addSelection(selNum); + doc->setSelectionColor(selNum, back); + if (invertText) + doc->setSelectionTextColor(selNum, palette().color(QPalette::HighlightedText)); +} + +/*! + \reimp +*/ +void Q3TextEdit::changeEvent(QEvent *ev) +{ + if(ev->type() == QEvent::ActivationChange) { + if (!isActiveWindow() && scrollTimer) + scrollTimer->stop(); + if (!palette().isEqual(QPalette::Active, QPalette::Inactive)) + updateContents(); + } + +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode && (ev->type() == QEvent::ApplicationFontChange + || ev->type() == QEvent::FontChange)) { + QFont f = font(); + if (f.kerning()) + f.setKerning(false); + + setFont(f); + + Q3ScrollView::setFont(f); + doc->setDefaultFormat(f, doc->formatCollection()->defaultFormat()->color()); + // recalculate the max string width + QFontMetrics fm(f); + int i, sw; + d->od->maxLineWidth = 0; + for (i = 0; i < d->od->numLines; i++) { + sw = fm.width(d->od->lines[LOGOFFSET(i)]); + if (d->od->maxLineWidth < sw) + d->od->maxLineWidth = sw; + } + resizeContents(d->od->maxLineWidth + 4, d->od->numLines * fm.lineSpacing() + 1); + return; + } +#endif + + Q3ScrollView::changeEvent(ev); + + if (textFormat() == Qt::PlainText) { + if (ev->type() == QEvent::ApplicationPaletteChange || ev->type() == QEvent::PaletteChange + || ev->type() == QEvent::EnabledChange) { + Q3TextFormat *f = doc->formatCollection()->defaultFormat(); + f->setColor(palette().text().color()); + updateContents(); + } + } + + if (ev->type() == QEvent::ApplicationFontChange || ev->type() == QEvent::FontChange) { + QFont f = font(); + if (f.kerning()) + f.setKerning(false); + doc->setMinimumWidth(-1); + doc->setDefaultFormat(f, doc->formatCollection()->defaultFormat()->color()); + lastFormatted = doc->firstParagraph(); + formatMore(); + repaintChanged(); + } +} + +void Q3TextEdit::setReadOnly(bool b) +{ + if (readonly == b) + return; + readonly = b; + d->cursorBlinkActive = !b; +#ifndef QT_NO_CURSOR + if (readonly) + viewport()->setCursor(Qt::ArrowCursor); + else + viewport()->setCursor(Qt::IBeamCursor); + setInputMethodEnabled(!readonly); +#endif +#ifdef QT_TEXTEDIT_OPTIMIZATION + checkOptimMode(); +#endif +} + +/*! + Scrolls to the bottom of the document and does formatting if + required. +*/ + +void Q3TextEdit::scrollToBottom() +{ + sync(); + setContentsPos(contentsX(), contentsHeight() - visibleHeight()); +} + +/*! + Returns the rectangle of the paragraph \a para in contents + coordinates, or an invalid rectangle if \a para is out of range. +*/ + +QRect Q3TextEdit::paragraphRect(int para) const +{ + Q3TextEdit *that = (Q3TextEdit *)this; + that->sync(); + Q3TextParagraph *p = doc->paragAt(para); + if (!p) + return QRect(-1, -1, -1, -1); + return p->rect(); +} + +/*! + Returns the paragraph which is at position \a pos (in contents + coordinates). +*/ + +int Q3TextEdit::paragraphAt(const QPoint &pos) const +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + QFontMetrics fm(Q3ScrollView::font()); + int parag = pos.y() / fm.lineSpacing(); + if (parag <= d->od->numLines) + return parag; + else + return 0; + } +#endif + Q3TextCursor c(doc); + c.place(pos, doc->firstParagraph()); + if (c.paragraph()) + return c.paragraph()->paragId(); + return -1; // should never happen.. +} + +/*! + Returns the index of the character (relative to its paragraph) at + position \a pos (in contents coordinates). If \a para is not 0, + \c{*}\a{para} is set to the character's paragraph. +*/ + +int Q3TextEdit::charAt(const QPoint &pos, int *para) const +{ +#ifdef QT_TEXTEDIT_OPTIMIZATION + if (d->optimMode) { + int par = paragraphAt(pos); + if (para) + *para = par; + return optimCharIndex(d->od->lines[LOGOFFSET(par)], pos.x()); + } +#endif + Q3TextCursor c(doc); + c.place(pos, doc->firstParagraph()); + if (c.paragraph()) { + if (para) + *para = c.paragraph()->paragId(); + return c.index(); + } + return -1; // should never happen.. +} + +/*! + Sets the background color of the paragraph \a para to \a bg. +*/ + +void Q3TextEdit::setParagraphBackgroundColor(int para, const QColor &bg) +{ + Q3TextParagraph *p = doc->paragAt(para); + if (!p) + return; + p->setBackgroundColor(bg); + repaintChanged(); +} + +/*! + Clears the background color of the paragraph \a para, so that the + default color is used again. +*/ + +void Q3TextEdit::clearParagraphBackground(int para) +{ + Q3TextParagraph *p = doc->paragAt(para); + if (!p) + return; + p->clearBackgroundColor(); + repaintChanged(); +} + +/*! + Returns the background color of the paragraph \a para or an + invalid color if \a para is out of range or the paragraph has no + background set +*/ + +QColor Q3TextEdit::paragraphBackgroundColor(int para) const +{ + Q3TextParagraph *p = doc->paragAt(para); + if (!p) + return QColor(); + QColor *c = p->backgroundColor(); + if (c) + return *c; + return QColor(); +} + +/*! + \property Q3TextEdit::undoRedoEnabled + \brief whether undo/redo is enabled + + When changing this property, the undo/redo history is cleared. + + The default is true. +*/ + +void Q3TextEdit::setUndoRedoEnabled(bool b) +{ + undoRedoInfo.clear(); + doc->commands()->clear(); + + undoEnabled = b; +} + +bool Q3TextEdit::isUndoRedoEnabled() const +{ + return undoEnabled; +} + +/*! + Returns true if undo is available; otherwise returns false. +*/ + +bool Q3TextEdit::isUndoAvailable() const +{ + return undoEnabled && (doc->commands()->isUndoAvailable() || undoRedoInfo.valid()); +} + +/*! + Returns true if redo is available; otherwise returns false. +*/ + +bool Q3TextEdit::isRedoAvailable() const +{ + return undoEnabled && doc->commands()->isRedoAvailable(); +} + +void Q3TextEdit::ensureFormatted(Q3TextParagraph *p) +{ + while (!p->isValid()) { + if (!lastFormatted) + return; + formatMore(); + } +} + +/*! \internal */ +void Q3TextEdit::updateCursor(const QPoint & pos) +{ + if (isReadOnly() && linksEnabled()) { + Q3TextCursor c = *cursor; + placeCursor(pos, &c, true); + +#ifndef QT_NO_NETWORKPROTOCOL + bool insideParagRect = true; + if (c.paragraph() == doc->lastParagraph() + && c.paragraph()->rect().y() + c.paragraph()->rect().height() < pos.y()) + insideParagRect = false; + if (insideParagRect && c.paragraph() && c.paragraph()->at(c.index()) && + c.paragraph()->at(c.index())->isAnchor()) { + if (!c.paragraph()->at(c.index())->anchorHref().isEmpty() + && c.index() < c.paragraph()->length() - 1) + onLink = c.paragraph()->at(c.index())->anchorHref(); + else + onLink.clear(); + + if (!c.paragraph()->at(c.index())->anchorName().isEmpty() + && c.index() < c.paragraph()->length() - 1) + d->onName = c.paragraph()->at(c.index())->anchorName(); + else + d->onName.clear(); + + if (!c.paragraph()->at(c.index())->anchorHref().isEmpty()) { +#ifndef QT_NO_CURSOR + viewport()->setCursor(onLink.isEmpty() ? Qt::ArrowCursor : Qt::PointingHandCursor); +#endif + QUrl u = QUrl(doc->context()).resolved(onLink); + emitHighlighted(u.toString(QUrl::None)); + } + } else { +#ifndef QT_NO_CURSOR + viewport()->setCursor(isReadOnly() ? Qt::ArrowCursor : Qt::IBeamCursor); +#endif + onLink.clear(); + emitHighlighted(QString()); + } +#endif + } +} + +/*! + Places the cursor \a c at the character which is closest to position + \a pos (in contents coordinates). If \a c is 0, the default text + cursor is used. + + \sa setCursorPosition() +*/ +void Q3TextEdit::placeCursor(const QPoint &pos, Q3TextCursor *c) +{ + placeCursor(pos, c, false); +} + +/*! \internal */ +void Q3TextEdit::clipboardChanged() +{ +#ifndef QT_NO_CLIPBOARD + // don't listen to selection changes + disconnect(QApplication::clipboard(), SIGNAL(selectionChanged()), this, 0); +#endif + selectAll(false); +} + +/*! \property Q3TextEdit::tabChangesFocus + \brief whether TAB changes focus or is accepted as input + + In some occasions text edits should not allow the user to input + tabulators or change indentation using the TAB key, as this breaks + the focus chain. The default is false. + +*/ + +void Q3TextEdit::setTabChangesFocus(bool b) +{ + d->tabChangesFocus = b; +} + +bool Q3TextEdit::tabChangesFocus() const +{ + return d->tabChangesFocus; +} + +#ifdef QT_TEXTEDIT_OPTIMIZATION +/* Implementation of optimized Qt::LogText mode follows */ + +static void qSwap(int * a, int * b) +{ + if (!a || !b) + return; + int tmp = *a; + *a = *b; + *b = tmp; +} + +/*! \internal */ +bool Q3TextEdit::checkOptimMode() +{ + bool oldMode = d->optimMode; + if (textFormat() == Qt::LogText) { + d->optimMode = true; + setReadOnly(true); + } else { + d->optimMode = false; + } + + // when changing mode - try to keep selections and text + if (oldMode != d->optimMode) { + if (d->optimMode) { + d->od = new Q3TextEditOptimPrivate; + connect(scrollTimer, SIGNAL(timeout()), this, SLOT(optimDoAutoScroll())); + disconnect(doc, SIGNAL(minimumWidthChanged(int)), this, SLOT(documentWidthChanged(int))); + disconnect(scrollTimer, SIGNAL(timeout()), this, SLOT(autoScrollTimerDone())); + disconnect(formatTimer, SIGNAL(timeout()), this, SLOT(formatMore())); + optimSetText(doc->originalText()); + doc->clear(true); + delete cursor; + cursor = new Q3TextCursor(doc); + } else { + disconnect(scrollTimer, SIGNAL(timeout()), this, SLOT(optimDoAutoScroll())); + connect(doc, SIGNAL(minimumWidthChanged(int)), this, SLOT(documentWidthChanged(int))); + connect(scrollTimer, SIGNAL(timeout()), this, SLOT(autoScrollTimerDone())); + connect(formatTimer, SIGNAL(timeout()), this, SLOT(formatMore())); + setText(optimText()); + delete d->od; + d->od = 0; + } + } + return d->optimMode; +} + +/*! \internal */ +QString Q3TextEdit::optimText() const +{ + QString str, tmp; + + if (d->od->len == 0) + return str; + + // concatenate all strings + int i; + int offset; + QMap<int,Q3TextEditOptimPrivate::Tag *>::ConstIterator it; + Q3TextEditOptimPrivate::Tag * ftag = 0; + for (i = 0; i < d->od->numLines; i++) { + if (d->od->lines[LOGOFFSET(i)].isEmpty()) { // CR lines are empty + str += QLatin1Char('\n'); + } else { + tmp = d->od->lines[LOGOFFSET(i)] + QLatin1Char('\n'); + // inject the tags for this line + if ((it = d->od->tagIndex.constFind(LOGOFFSET(i))) != d->od->tagIndex.constEnd()) + ftag = it.value(); + offset = 0; + while (ftag && ftag->line == i) { + tmp.insert(ftag->index + offset, QLatin1Char('<') + ftag->tag + QLatin1Char('>')); + offset += ftag->tag.length() + 2; // 2 -> the '<' and '>' chars + ftag = ftag->next; + } + str += tmp; + } + } + return str; +} + +/*! \internal */ +void Q3TextEdit::optimSetText(const QString &str) +{ + optimRemoveSelection(); +// this is just too slow - but may have to go in due to compatibility reasons +// if (str == optimText()) +// return; + d->od->numLines = 0; + d->od->lines.clear(); + d->od->maxLineWidth = 0; + d->od->len = 0; + d->od->clearTags(); + QFontMetrics fm(Q3ScrollView::font()); + if (!(str.isEmpty() || str.isNull() || d->maxLogLines == 0)) { + QStringList strl = str.split(QLatin1Char('\n')); + int lWidth = 0; + for (QStringList::Iterator it = strl.begin(); it != strl.end(); ++it) { + optimParseTags(&*it); + optimCheckLimit(*it); + lWidth = fm.width(*it); + if (lWidth > d->od->maxLineWidth) + d->od->maxLineWidth = lWidth; + } + } + resizeContents(d->od->maxLineWidth + 4, d->od->numLines * fm.lineSpacing() + 1); + repaintContents(); + emit textChanged(); +} + +/*! \internal + + Append \a tag to the tag list. +*/ +Q3TextEditOptimPrivate::Tag * Q3TextEdit::optimAppendTag(int index, const QString & tag) +{ + Q3TextEditOptimPrivate::Tag * t = new Q3TextEditOptimPrivate::Tag, * tmp; + + if (d->od->tags == 0) + d->od->tags = t; + t->bold = t->italic = t->underline = false; + t->line = d->od->numLines; + t->index = index; + t->tag = tag; + t->leftTag = 0; + t->parent = 0; + t->prev = d->od->lastTag; + if (d->od->lastTag) + d->od->lastTag->next = t; + t->next = 0; + d->od->lastTag = t; + tmp = d->od->tagIndex[LOGOFFSET(t->line)]; + if (!tmp || (tmp && tmp->index > t->index)) { + d->od->tagIndex.insert(LOGOFFSET(t->line), t); + } + return t; +} + +/*! \internal + + Insert \a tag in the tag - according to line and index numbers +*/ +Q3TextEditOptimPrivate::Tag *Q3TextEdit::optimInsertTag(int line, int index, const QString &tag) +{ + Q3TextEditOptimPrivate::Tag *t = new Q3TextEditOptimPrivate::Tag, *tmp; + + if (d->od->tags == 0) + d->od->tags = t; + t->bold = t->italic = t->underline = false; + t->line = line; + t->index = index; + t->tag = tag; + t->leftTag = 0; + t->parent = 0; + t->next = 0; + t->prev = 0; + + // find insertion pt. in tag struct. + QMap<int,Q3TextEditOptimPrivate::Tag *>::ConstIterator it; + if ((it = d->od->tagIndex.constFind(LOGOFFSET(line))) != d->od->tagIndex.constEnd()) { + tmp = *it; + if (tmp->index >= index) { // the existing tag may be placed AFTER the one we want to insert + tmp = tmp->prev; + } else { + while (tmp && tmp->next && tmp->next->line == line && tmp->next->index <= index) + tmp = tmp->next; + } + } else { + tmp = d->od->tags; + while (tmp && tmp->next && tmp->next->line < line) + tmp = tmp->next; + if (tmp == d->od->tags) + tmp = 0; + } + + t->prev = tmp; + t->next = tmp ? tmp->next : 0; + if (t->next) + t->next->prev = t; + if (tmp) + tmp->next = t; + + tmp = d->od->tagIndex[LOGOFFSET(t->line)]; + if (!tmp || (tmp && tmp->index >= t->index)) { + d->od->tagIndex.insert(LOGOFFSET(t->line), t); + } + return t; +} + +/*! \internal + + Find tags in \a line, remove them from \a line and put them in a + structure. + + A tag is delimited by '<' and '>'. The characters '<', '>' and '&' + are escaped by using '<', '>' and '&'. Left-tags marks + the starting point for formatting, while right-tags mark the ending + point. A right-tag is the same as a left-tag, but with a '/' + appearing before the tag keyword. E.g a valid left-tag: <b>, and + a valid right-tag: </b>. Tags can be nested, but they have to be + closed in the same order as they are opened. E.g: + <font color=red><font color=blue>blue</font>red</font> - is valid, while: + <font color=red><b>bold red</font> just bold</b> - is invalid since the font tag is + closed before the bold tag. Note that a tag does not have to be + closed: '<font color=blue>Lots of text - and then some..' is perfectly valid for + setting all text appearing after the tag to blue. A tag can be used + to change the color of a piece of text, or set one of the following + formatting attributes: bold, italic and underline. These attributes + are set using the <b>, <i> and <u> tags. Example of valid tags: + <font color=red>, </font>, <b>, <u>, <i>, </i>. + Example of valid text: + This is some <font color=red>red text</font>, while this is some <font color=green>green + text</font>. <font color=blue><font color=yellow>This is yellow</font>, while this is + blue.</font> + + Note that only the color attribute of the HTML font tag is supported. + + Limitations: + 1. A tag cannot span several lines. + 2. Very limited error checking - mismatching left/right-tags is the + only thing that is detected. + +*/ +void Q3TextEdit::optimParseTags(QString * line, int lineNo, int indexOffset) +{ + int len = line->length(); + int i, startIndex = -1, endIndex = -1, escIndex = -1; + int state = 0; // 0 = outside tag, 1 = inside tag + bool tagOpen, tagClose; + int bold = 0, italic = 0, underline = 0; + QString tagStr; + QStack<Q3TextEditOptimPrivate::Tag *> tagStack; + + for (i = 0; i < len; i++) { + tagOpen = (*line)[i] == QLatin1Char('<'); + tagClose = (*line)[i] == QLatin1Char('>'); + + // handle '<' and '>' and '&' + if ((*line)[i] == QLatin1Char('&')) { + escIndex = i; + continue; + } else if (escIndex != -1 && (*line)[i] == QLatin1Char(';')) { + QString esc = line->mid(escIndex, i - escIndex + 1); + QString c; + if (esc == QLatin1String("<")) + c = QLatin1Char('<'); + else if (esc == QLatin1String(">")) + c = QLatin1Char('>'); + else if (esc == QLatin1String("&")) + c = QLatin1Char('&'); + line->replace(escIndex, i - escIndex + 1, c); + len = line->length(); + i -= i-escIndex; + escIndex = -1; + continue; + } + + if (state == 0 && tagOpen) { + state = 1; + startIndex = i; + continue; + } + if (state == 1 && tagClose) { + state = 0; + endIndex = i; + if (!tagStr.isEmpty()) { + Q3TextEditOptimPrivate::Tag * tag, * cur, * tmp; + bool format = true; + + if (tagStr == QLatin1String("b")) + bold++; + else if (tagStr == QLatin1String("/b")) + bold--; + else if (tagStr == QLatin1String("i")) + italic++; + else if (tagStr == QLatin1String("/i")) + italic--; + else if (tagStr == QLatin1String("u")) + underline++; + else if (tagStr == QLatin1String("/u")) + underline--; + else + format = false; + if (lineNo > -1) + tag = optimInsertTag(lineNo, startIndex + indexOffset, tagStr); + else + tag = optimAppendTag(startIndex, tagStr); + // everything that is not a b, u or i tag is considered + // to be a color tag. + tag->type = format ? Q3TextEditOptimPrivate::Format + : Q3TextEditOptimPrivate::Color; + if (tagStr[0] == QLatin1Char('/')) { + // this is a right-tag - search for the left-tag + // and possible parent tag + cur = tag->prev; + if (!cur) { + qWarning("Q3TextEdit::optimParseTags: no left-tag for '<%s>' in line %d.", + tag->tag.latin1(), tag->line + 1); + return; // something is wrong - give up + } + while (cur) { + if (cur->leftTag) { // push right-tags encountered + tagStack.push(cur); + } else { + tmp = tagStack.isEmpty() ? 0 : tagStack.pop(); + if (!tmp) { + if (((QLatin1Char('/') + cur->tag) == tag->tag) || + (tag->tag == QLatin1String("/font") && cur->tag.left(4) == QLatin1String("font"))) { + // set up the left and parent of this tag + tag->leftTag = cur; + tmp = cur->prev; + if (tmp && tmp->parent) { + tag->parent = tmp->parent; + } else if (tmp && !tmp->leftTag) { + tag->parent = tmp; + } + break; + } else if (!cur->leftTag) { + qWarning("Q3TextEdit::optimParseTags: mismatching %s-tag for '<%s>' in line %d.", + qPrintable(QString(cur->tag[0] == QLatin1Char('/') ? QLatin1String("left") : QLatin1String("right"))), + cur->tag.latin1(), cur->line + 1); + return; // something is amiss - give up + } + } + } + cur = cur->prev; + } + } else { + tag->bold = bold > 0; + tag->italic = italic > 0; + tag->underline = underline > 0; + tmp = tag->prev; + while (tmp && tmp->leftTag) { + tmp = tmp->leftTag->parent; + } + if (tmp) { + tag->bold |= tmp->bold; + tag->italic |= tmp->italic; + tag->underline |= tmp->underline; + } + } + } + if (startIndex != -1) { + int l = (endIndex == -1) ? + line->length() - startIndex : endIndex - startIndex; + line->remove(startIndex, l+1); + len = line->length(); + i -= l+1; + } + tagStr = QLatin1String(""); + continue; + } + + if (state == 1) { + tagStr += (*line)[i]; + } + } +} + +// calculate the width of a string in pixels inc. tabs +static int qStrWidth(const QString& str, int tabWidth, const QFontMetrics& fm) +{ + int tabs = str.count(QLatin1Char('\t')); + + if (!tabs) + return fm.width(str); + + int newIdx = 0; + int lastIdx = 0; + int strWidth = 0; + int tn; + for (tn = 1; tn <= tabs; ++tn) { + newIdx = str.indexOf(QLatin1Char('\t'), newIdx); + strWidth += fm.width(str.mid(lastIdx, newIdx - lastIdx)); + if (strWidth >= tn * tabWidth) { + int u = tn; + while (strWidth >= u * tabWidth) + ++u; + strWidth = u * tabWidth; + } else { + strWidth = tn * tabWidth; + } + lastIdx = ++newIdx; + } + if ((int)str.length() > newIdx) + strWidth += fm.width(str.mid(newIdx)); + return strWidth; +} + +bool Q3TextEdit::optimHasBoldMetrics(int line) +{ + Q3TextEditOptimPrivate::Tag *t; + QMap<int,Q3TextEditOptimPrivate::Tag *>::ConstIterator it; + if ((it = d->od->tagIndex.constFind(line)) != d->od->tagIndex.constEnd()) { + t = *it; + while (t && t->line == line) { + if (t->bold) + return true; + t = t->next; + } + } else if ((t = optimPreviousLeftTag(line)) && t->bold) { + return true; + } + return false; +} + +/*! \internal + + Append \a str to the current text buffer. Parses each line to find + formatting tags. +*/ +void Q3TextEdit::optimAppend(const QString &str) +{ + if (str.isEmpty() || str.isNull() || d->maxLogLines == 0) + return; + + QStringList strl = str.split(QLatin1Char('\n')); + QStringList::Iterator it = strl.begin(); + + QFontMetrics fm(Q3ScrollView::font()); + int lWidth = 0; + for (; it != strl.end(); ++it) { + optimParseTags(&*it); + optimCheckLimit(*it); + if (optimHasBoldMetrics(d->od->numLines-1)) { + QFont fnt = Q3ScrollView::font(); + fnt.setBold(true); + fm = QFontMetrics(fnt); + } + lWidth = qStrWidth(*it, tabStopWidth(), fm) + 4; + if (lWidth > d->od->maxLineWidth) + d->od->maxLineWidth = lWidth; + } + bool scrollToEnd = contentsY() >= contentsHeight() - visibleHeight(); + resizeContents(d->od->maxLineWidth + 4, d->od->numLines * fm.lineSpacing() + 1); + if (scrollToEnd) { + updateScrollBars(); + ensureVisible(contentsX(), contentsHeight(), 0, 0); + } + // when a max log size is set, the text may not be redrawn because + // the size of the viewport may not have changed + if (d->maxLogLines > -1) + viewport()->update(); + emit textChanged(); +} + +static void qStripTags(QString *line) +{ + int len = line->length(); + int i, startIndex = -1, endIndex = -1, escIndex = -1; + int state = 0; // 0 = outside tag, 1 = inside tag + bool tagOpen, tagClose; + + for (i = 0; i < len; i++) { + tagOpen = (*line)[i] == QLatin1Char('<'); + tagClose = (*line)[i] == QLatin1Char('>'); + + // handle '<' and '>' and '&' + if ((*line)[i] == QLatin1Char('&')) { + escIndex = i; + continue; + } else if (escIndex != -1 && (*line)[i] == QLatin1Char(';')) { + QString esc = line->mid(escIndex, i - escIndex + 1); + QString c; + if (esc == QLatin1String("<")) + c = QLatin1Char('<'); + else if (esc == QLatin1String(">")) + c = QLatin1Char('>'); + else if (esc == QLatin1String("&")) + c = QLatin1Char('&'); + line->replace(escIndex, i - escIndex + 1, c); + len = line->length(); + i -= i-escIndex; + escIndex = -1; + continue; + } + + if (state == 0 && tagOpen) { + state = 1; + startIndex = i; + continue; + } + if (state == 1 && tagClose) { + state = 0; + endIndex = i; + if (startIndex != -1) { + int l = (endIndex == -1) ? + line->length() - startIndex : endIndex - startIndex; + line->remove(startIndex, l+1); + len = line->length(); + i -= l+1; + } + continue; + } + } +} + +/*! \internal + + Inserts the text into \a line at index \a index. +*/ + +void Q3TextEdit::optimInsert(const QString& text, int line, int index) +{ + if (text.isEmpty() || d->maxLogLines == 0) + return; + if (line < 0) + line = 0; + if (line > d->od->numLines-1) + line = d->od->numLines-1; + if (index < 0) + index = 0; + if (index > d->od->lines[line].length()) + index = d->od->lines[line].length(); + + QStringList strl = text.split(QLatin1Char('\n')); + int numNewLines = strl.count() - 1; + Q3TextEditOptimPrivate::Tag *tag = 0; + QMap<int,Q3TextEditOptimPrivate::Tag *>::ConstIterator ii; + int x; + + if (numNewLines == 0) { + // Case 1. Fast single line case - just inject it! + QString stripped = text; + qStripTags(&stripped); + d->od->lines[LOGOFFSET(line)].insert(index, stripped); + // move the tag indices following the insertion pt. + if ((ii = d->od->tagIndex.constFind(LOGOFFSET(line))) != d->od->tagIndex.constEnd()) { + tag = *ii; + while (tag && (LOGOFFSET(tag->line) == line && tag->index < index)) + tag = tag->next; + while (tag && (LOGOFFSET(tag->line) == line)) { + tag->index += stripped.length(); + tag = tag->next; + } + } + stripped = text; + optimParseTags(&stripped, line, index); + } else if (numNewLines > 0) { + // Case 2. We have at least 1 newline char - split at + // insertion pt. and make room for new lines - complex and slow! + QString left = d->od->lines[LOGOFFSET(line)].left(index); + QString right = d->od->lines[LOGOFFSET(line)].mid(index); + + // rearrange lines for insertion + for (x = d->od->numLines - 1; x > line; x--) + d->od->lines[x + numNewLines] = d->od->lines[x]; + d->od->numLines += numNewLines; + + // fix the tag index and the tag line/index numbers - this + // might take a while.. + for (x = line; x < d->od->numLines; x++) { + if ((ii = d->od->tagIndex.constFind(LOGOFFSET(line))) != d->od->tagIndex.constEnd()) { + tag = ii.value(); + if (LOGOFFSET(tag->line) == line) + while (tag && (LOGOFFSET(tag->line) == line && tag->index < index)) + tag = tag->next; + } + } + + // relabel affected tags with new line numbers and new index + // positions + while (tag) { + if (LOGOFFSET(tag->line) == line) + tag->index -= index; + tag->line += numNewLines; + tag = tag->next; + } + + // generate a new tag index + d->od->tagIndex.clear(); + tag = d->od->tags; + while (tag) { + if (!((ii = d->od->tagIndex.constFind(LOGOFFSET(tag->line))) != d->od->tagIndex.constEnd())) + d->od->tagIndex[LOGOFFSET(tag->line)] = tag; + tag = tag->next; + } + + // update the tag indices on the spliced line - needs to be done before new tags are added + QString stripped = strl[strl.count() - 1]; + qStripTags(&stripped); + if ((ii = d->od->tagIndex.constFind(LOGOFFSET(line + numNewLines))) != d->od->tagIndex.constEnd()) { + tag = *ii; + while (tag && (LOGOFFSET(tag->line) == line + numNewLines)) { + tag->index += stripped.length(); + tag = tag->next; + } + } + + // inject the new lines + QStringList::Iterator it = strl.begin(); + x = line; + int idx; + for (; it != strl.end(); ++it) { + stripped = *it; + qStripTags(&stripped); + if (x == line) { + stripped = left + stripped; + idx = index; + } else { + idx = 0; + } + d->od->lines[LOGOFFSET(x)] = stripped; + optimParseTags(&*it, x++, idx); + } + d->od->lines[LOGOFFSET(x - 1)] += right; + } + // recalculate the pixel width of the longest injected line - + QFontMetrics fm(Q3ScrollView::font()); + int lWidth = 0; + for (x = line; x < line + numNewLines; x++) { + if (optimHasBoldMetrics(x)) { + QFont fnt = Q3ScrollView::font(); + fnt.setBold(true); + fm = QFontMetrics(fnt); + } + lWidth = fm.width(d->od->lines[x]) + 4; + if (lWidth > d->od->maxLineWidth) + d->od->maxLineWidth = lWidth; + } + resizeContents(d->od->maxLineWidth + 4, d->od->numLines * fm.lineSpacing() + 1); + repaintContents(); + emit textChanged(); +} + + +/*! \internal + + Returns the first open left-tag appearing before line \a line. + */ +Q3TextEditOptimPrivate::Tag * Q3TextEdit::optimPreviousLeftTag(int line) +{ + Q3TextEditOptimPrivate::Tag * ftag = 0; + QMap<int,Q3TextEditOptimPrivate::Tag *>::ConstIterator it; + if ((it = d->od->tagIndex.constFind(LOGOFFSET(line))) != d->od->tagIndex.constEnd()) + ftag = it.value(); + if (!ftag) { + // start searching for an open tag + ftag = d->od->tags; + while (ftag) { + if (ftag->line > line || ftag->next == 0) { + if (ftag->line > line) + ftag = ftag->prev; + break; + } + ftag = ftag->next; + } + } else { + ftag = ftag->prev; + } + + if (ftag) { + if (ftag && ftag->parent) // use the open parent tag + ftag = ftag->parent; + else if (ftag && ftag->leftTag) // this is a right-tag with no parent + ftag = 0; + } + return ftag; +} + +/*! \internal + + Set the format for the string starting at index \a start and ending + at \a end according to \a tag. If \a tag is a Format tag, find the + first open color tag appearing before \a tag and use that tag to + color the string. +*/ +void Q3TextEdit::optimSetTextFormat(Q3TextDocument * td, Q3TextCursor * cur, + Q3TextFormat * f, int start, int end, + Q3TextEditOptimPrivate::Tag * tag) +{ + int formatFlags = Q3TextFormat::Bold | Q3TextFormat::Italic | + Q3TextFormat::Underline; + cur->setIndex(start); + td->setSelectionStart(0, *cur); + cur->setIndex(end); + td->setSelectionEnd(0, *cur); + Q3StyleSheetItem * ssItem = styleSheet()->item(tag->tag); + if (!ssItem || tag->type == Q3TextEditOptimPrivate::Format) { + f->setBold(tag->bold); + f->setItalic(tag->italic); + f->setUnderline(tag->underline); + if (tag->type == Q3TextEditOptimPrivate::Format) { + // check to see if there are any open color tags prior to + // this format tag + tag = tag->prev; + while (tag && (tag->type == Q3TextEditOptimPrivate::Format || + tag->leftTag)) { + tag = tag->leftTag ? tag->parent : tag->prev; + } + } + if (tag) { + QString col = tag->tag.simplified(); + if (col.left(10) == QLatin1String("font color")) { + int i = col.indexOf(QLatin1Char('='), 10); + col = col.mid(i + 1).simplified(); + if (col[0] == QLatin1Char('\"')) + col = col.mid(1, col.length() - 2); + } + QColor color = QColor(col); + if (color.isValid()) { + formatFlags |= Q3TextFormat::Color; + f->setColor(color); + } + } + } else { // use the stylesheet tag definition + if (ssItem->color().isValid()) { + formatFlags |= Q3TextFormat::Color; + f->setColor(ssItem->color()); + } + f->setBold(ssItem->fontWeight() == QFont::Bold); + f->setItalic(ssItem->fontItalic()); + f->setUnderline(ssItem->fontUnderline()); + } + td->setFormat(0, f, formatFlags); + td->removeSelection(0); +} + +/*! \internal */ +void Q3TextEdit::optimDrawContents(QPainter * p, int clipx, int clipy, + int clipw, int cliph) +{ + QFontMetrics fm(Q3ScrollView::font()); + int startLine = clipy / fm.lineSpacing(); + + // we always have to fetch at least two lines for drawing because the + // painter may be translated so that parts of two lines cover the area + // of a single line + int nLines = (cliph / fm.lineSpacing()) + 2; + int endLine = startLine + nLines; + + if (startLine >= d->od->numLines) + return; + if ((startLine + nLines) > d->od->numLines) + nLines = d->od->numLines - startLine; + + int i = 0; + QString str; + for (i = startLine; i < (startLine + nLines); i++) + str.append(d->od->lines[LOGOFFSET(i)] + QLatin1Char('\n')); + + Q3TextDocument * td = new Q3TextDocument(0); + td->setDefaultFormat(Q3ScrollView::font(), QColor()); + td->setPlainText(str); + td->setFormatter(new Q3TextFormatterBreakWords); // deleted by QTextDoc + td->formatter()->setWrapEnabled(false); + td->setTabStops(doc->tabStopWidth()); + + // get the current text color from the current format + td->selectAll(Q3TextDocument::Standard); + Q3TextFormat f; + f.setColor(palette().text().color()); + f.setFont(Q3ScrollView::font()); + td->setFormat(Q3TextDocument::Standard, &f, + Q3TextFormat::Color | Q3TextFormat::Font); + td->removeSelection(Q3TextDocument::Standard); + + // add tag formatting + if (d->od->tags) { + int i = startLine; + QMap<int,Q3TextEditOptimPrivate::Tag *>::ConstIterator it; + Q3TextEditOptimPrivate::Tag * tag = 0, * tmp = 0; + Q3TextCursor cur(td); + // Step 1 - find previous left-tag + tmp = optimPreviousLeftTag(i); + for (; i < startLine + nLines; i++) { + if ((it = d->od->tagIndex.constFind(LOGOFFSET(i))) != d->od->tagIndex.constEnd()) + tag = it.value(); + // Step 2 - iterate over tags on the current line + int lastIndex = 0; + while (tag && tag->line == i) { + tmp = 0; + if (tag->prev && !tag->prev->leftTag) { + tmp = tag->prev; + } else if (tag->prev && tag->prev->parent) { + tmp = tag->prev->parent; + } + if ((tag->index - lastIndex) > 0 && tmp) { + optimSetTextFormat(td, &cur, &f, lastIndex, tag->index, tmp); + } + lastIndex = tag->index; + tmp = tag; + tag = tag->next; + } + // Step 3 - color last part of the line - if necessary + if (tmp && tmp->parent) + tmp = tmp->parent; + if ((cur.paragraph()->length()-1 - lastIndex) > 0 && tmp && !tmp->leftTag) { + optimSetTextFormat(td, &cur, &f, lastIndex, + cur.paragraph()->length() - 1, tmp); + } + cur.setParagraph(cur.paragraph()->next()); + } + // useful debug info + // +// tag = d->od->tags; +// qWarning("###"); +// while (tag) { +// qWarning("Tag: %p, parent: %09p, leftTag: %09p, Name: %-15s, ParentName: %s, %d%d%d", tag, +// tag->parent, tag->leftTag, tag->tag.latin1(), tag->parent ? tag->parent->tag.latin1():"<none>", +// tag->bold, tag->italic, tag->underline); +// tag = tag->next; +// } + } + + // if there is a selection, make sure that the selection in the + // part we need to redraw is set correctly + if (optimHasSelection()) { + Q3TextCursor c1(td); + Q3TextCursor c2(td); + int selStart = d->od->selStart.line; + int idxStart = d->od->selStart.index; + int selEnd = d->od->selEnd.line; + int idxEnd = d->od->selEnd.index; + if (selEnd < selStart) { + qSwap(&selStart, &selEnd); + qSwap(&idxStart, &idxEnd); + } + if (selEnd > d->od->numLines-1) { + selEnd = d->od->numLines-1; + } + if (startLine <= selStart && endLine >= selEnd) { + // case 1: area to paint covers entire selection + int paragS = selStart - startLine; + int paragE = paragS + (selEnd - selStart); + Q3TextParagraph * parag = td->paragAt(paragS); + if (parag) { + c1.setParagraph(parag); + if (td->text(paragS).length() >= idxStart) + c1.setIndex(idxStart); + } + parag = td->paragAt(paragE); + if (parag) { + c2.setParagraph(parag); + if (td->text(paragE).length() >= idxEnd) + c2.setIndex(idxEnd); + } + } else if (startLine > selStart && endLine < selEnd) { + // case 2: area to paint is all part of the selection + td->selectAll(Q3TextDocument::Standard); + } else if (startLine > selStart && endLine >= selEnd && + startLine <= selEnd) { + // case 3: area to paint starts inside a selection, ends past it + c1.setParagraph(td->firstParagraph()); + c1.setIndex(0); + int paragE = selEnd - startLine; + Q3TextParagraph * parag = td->paragAt(paragE); + if (parag) { + c2.setParagraph(parag); + if (td->text(paragE).length() >= idxEnd) + c2.setIndex(idxEnd); + } + } else if (startLine <= selStart && endLine < selEnd && + endLine > selStart) { + // case 4: area to paint starts before a selection, ends inside it + int paragS = selStart - startLine; + Q3TextParagraph * parag = td->paragAt(paragS); + if (parag) { + c1.setParagraph(parag); + c1.setIndex(idxStart); + } + c2.setParagraph(td->lastParagraph()); + c2.setIndex(td->lastParagraph()->string()->toString().length() - 1); + + } + // previously selected? + if (!td->hasSelection(Q3TextDocument::Standard)) { + td->setSelectionStart(Q3TextDocument::Standard, c1); + td->setSelectionEnd(Q3TextDocument::Standard, c2); + } + } + td->doLayout(p, contentsWidth()); + + // have to align the painter so that partly visible lines are + // drawn at the correct position within the area that needs to be + // painted + int offset = clipy % fm.lineSpacing() + 2; + QRect r(clipx, 0, clipw, cliph + offset); + p->translate(0, clipy - offset); + td->draw(p, r.x(), r.y(), r.width(), r.height(), palette()); + p->translate(0, -(clipy - offset)); + delete td; +} + +/*! \internal */ +void Q3TextEdit::optimMousePressEvent(QMouseEvent * e) +{ + if (e->button() != Qt::LeftButton) + return; + + QFontMetrics fm(Q3ScrollView::font()); + mousePressed = true; + mousePos = e->pos(); + d->od->selStart.line = e->y() / fm.lineSpacing(); + if (d->od->selStart.line > d->od->numLines-1) { + d->od->selStart.line = d->od->numLines-1; + d->od->selStart.index = d->od->lines[LOGOFFSET(d->od->numLines-1)].length(); + } else { + QString str = d->od->lines[LOGOFFSET(d->od->selStart.line)]; + d->od->selStart.index = optimCharIndex(str, mousePos.x()); + } + d->od->selEnd.line = d->od->selStart.line; + d->od->selEnd.index = d->od->selStart.index; + oldMousePos = e->pos(); + repaintContents(); +} + +/*! \internal */ +void Q3TextEdit::optimMouseReleaseEvent(QMouseEvent * e) +{ + if (e->button() != Qt::LeftButton) + return; + + if (scrollTimer->isActive()) + scrollTimer->stop(); + if (!inDoubleClick) { + QFontMetrics fm(Q3ScrollView::font()); + d->od->selEnd.line = e->y() / fm.lineSpacing(); + if (d->od->selEnd.line > d->od->numLines-1) { + d->od->selEnd.line = d->od->numLines-1; + } + QString str = d->od->lines[LOGOFFSET(d->od->selEnd.line)]; + mousePos = e->pos(); + d->od->selEnd.index = optimCharIndex(str, mousePos.x()); + if (d->od->selEnd.line < d->od->selStart.line) { + qSwap(&d->od->selStart.line, &d->od->selEnd.line); + qSwap(&d->od->selStart.index, &d->od->selEnd.index); + } else if (d->od->selStart.line == d->od->selEnd.line && + d->od->selStart.index > d->od->selEnd.index) { + qSwap(&d->od->selStart.index, &d->od->selEnd.index); + } + oldMousePos = e->pos(); + repaintContents(); + } + if (mousePressed) { + mousePressed = false; + copyToClipboard(); + } + + inDoubleClick = false; + emit copyAvailable(optimHasSelection()); + emit selectionChanged(); +} + +/*! \internal */ +void Q3TextEdit::optimMouseMoveEvent(QMouseEvent * e) +{ + mousePos = e->pos(); + optimDoAutoScroll(); + oldMousePos = mousePos; +} + +/*! \internal */ +void Q3TextEdit::optimDoAutoScroll() +{ + if (!mousePressed) + return; + + QFontMetrics fm(Q3ScrollView::font()); + QPoint pos(mapFromGlobal(QCursor::pos())); + bool doScroll = false; + int xx = contentsX() + pos.x(); + int yy = contentsY() + pos.y(); + + // find out how much we have to scroll in either dir. + if (pos.x() < 0 || pos.x() > viewport()->width() || + pos.y() < 0 || pos.y() > viewport()->height()) { + int my = yy; + if (pos.x() < 0) + xx = contentsX() - fm.width(QLatin1Char('w')); + else if (pos.x() > viewport()->width()) + xx = contentsX() + viewport()->width() + fm.width(QLatin1Char('w')); + + if (pos.y() < 0) { + my = contentsY() - 1; + yy = (my / fm.lineSpacing()) * fm.lineSpacing() + 1; + } else if (pos.y() > viewport()->height()) { + my = contentsY() + viewport()->height() + 1; + yy = (my / fm.lineSpacing() + 1) * fm.lineSpacing() - 1; + } + d->od->selEnd.line = my / fm.lineSpacing(); + mousePos.setX(xx); + mousePos.setY(my); + doScroll = true; + } else { + d->od->selEnd.line = mousePos.y() / fm.lineSpacing(); + } + + if (d->od->selEnd.line < 0) { + d->od->selEnd.line = 0; + } else if (d->od->selEnd.line > d->od->numLines-1) { + d->od->selEnd.line = d->od->numLines-1; + } + + QString str = d->od->lines[LOGOFFSET(d->od->selEnd.line)]; + d->od->selEnd.index = optimCharIndex(str, mousePos.x()); + + // have to have a valid index before generating a paint event + if (doScroll) + ensureVisible(xx, yy, 1, 1); + + // if the text document is smaller than the height of the viewport + // - redraw the whole thing otherwise calculate the rect that + // needs drawing. + if (d->od->numLines * fm.lineSpacing() < viewport()->height()) { + repaintContents(contentsX(), contentsY(), width(), height()); + } else { + int h = QABS(mousePos.y() - oldMousePos.y()) + fm.lineSpacing() * 2; + int y; + if (oldMousePos.y() < mousePos.y()) { + y = oldMousePos.y() - fm.lineSpacing(); + } else { + // expand paint area for a fully selected line + h += fm.lineSpacing(); + y = mousePos.y() - fm.lineSpacing()*2; + } + if (y < 0) + y = 0; + repaintContents(contentsX(), y, width(), h); + } + + if ((!scrollTimer->isActive() && pos.y() < 0) || pos.y() > height()) + scrollTimer->start(100, false); + else if (scrollTimer->isActive() && pos.y() >= 0 && pos.y() <= height()) + scrollTimer->stop(); +} + +/*! \internal + + Returns the index of the character in the string \a str that is + currently under the mouse pointer. +*/ +int Q3TextEdit::optimCharIndex(const QString &str, int mx) const +{ + QFontMetrics fm(Q3ScrollView::font()); + int i = 0; + int dd, dist = 10000000; + int curpos = 0; + int strWidth; + mx = mx - 4; // ### get the real margin from somewhere + + if (!str.contains(QLatin1Char('\t')) && mx > fm.width(str)) + return str.length(); + + while (i < str.length()) { + strWidth = qStrWidth(str.left(i), tabStopWidth(), fm); + dd = strWidth - mx; + if (QABS(dd) <= dist) { + dist = QABS(dd); + if (mx >= strWidth) + curpos = i; + } + ++i; + } + return curpos; +} + +/*! \internal */ +void Q3TextEdit::optimSelectAll() +{ + d->od->selStart.line = d->od->selStart.index = 0; + d->od->selEnd.line = d->od->numLines - 1; + d->od->selEnd.index = d->od->lines[LOGOFFSET(d->od->selEnd.line)].length(); + + repaintContents(); + emit copyAvailable(optimHasSelection()); + emit selectionChanged(); +} + +/*! \internal */ +void Q3TextEdit::optimRemoveSelection() +{ + d->od->selStart.line = d->od->selEnd.line = -1; + d->od->selStart.index = d->od->selEnd.index = -1; + repaintContents(); +} + +/*! \internal */ +void Q3TextEdit::optimSetSelection(int startLine, int startIdx, + int endLine, int endIdx) +{ + d->od->selStart.line = startLine; + d->od->selEnd.line = endLine; + d->od->selStart.index = startIdx; + d->od->selEnd.index = endIdx; +} + +/*! \internal */ +bool Q3TextEdit::optimHasSelection() const +{ + if (d->od->selStart.line != d->od->selEnd.line || + d->od->selStart.index != d->od->selEnd.index) + return true; + return false; +} + +/*! \internal */ +QString Q3TextEdit::optimSelectedText() const +{ + QString str; + + if (!optimHasSelection()) + return str; + + // concatenate all strings + if (d->od->selStart.line == d->od->selEnd.line) { + str = d->od->lines[LOGOFFSET(d->od->selEnd.line)].mid(d->od->selStart.index, + d->od->selEnd.index - d->od->selStart.index); + } else { + int i = d->od->selStart.line; + str = d->od->lines[LOGOFFSET(i)].right(d->od->lines[LOGOFFSET(i)].length() - + d->od->selStart.index) + QLatin1Char('\n'); + i++; + for (; i < d->od->selEnd.line; i++) { + if (d->od->lines[LOGOFFSET(i)].isEmpty()) // CR lines are empty + str += QLatin1Char('\n'); + else + str += d->od->lines[LOGOFFSET(i)] + QLatin1Char('\n'); + } + str += d->od->lines[LOGOFFSET(d->od->selEnd.line)].left(d->od->selEnd.index); + } + return str; +} + +/*! \internal */ +bool Q3TextEdit::optimFind(const QString & expr, bool cs, bool /*wo*/, + bool fw, int * para, int * index) +{ + bool found = false; + int parag = para ? *para : d->od->search.line, + idx = index ? *index : d->od->search.index, i; + + if (d->od->len == 0) + return false; + + for (i = parag; fw ? i < d->od->numLines : i >= 0; fw ? i++ : i--) { + idx = fw + ? d->od->lines[LOGOFFSET(i)].indexOf(expr, idx, + cs ? Qt::CaseSensitive : Qt::CaseInsensitive) + : d->od->lines[LOGOFFSET(i)].lastIndexOf(expr, idx, + cs ? Qt::CaseSensitive : Qt::CaseInsensitive); + if (idx != -1) { + found = true; + break; + } else if (fw) + idx = 0; + } + + if (found) { + if (index) + *index = idx; + if (para) + *para = i; + d->od->search.index = idx; + d->od->search.line = i; + optimSetSelection(i, idx, i, idx + expr.length()); + QFontMetrics fm(Q3ScrollView::font()); + int h = fm.lineSpacing(); + int x = fm.width(d->od->lines[LOGOFFSET(i)].left(idx + expr.length())) + 4; + ensureVisible(x, i * h + h / 2, 1, h / 2 + 2); + repaintContents(); // could possibly be optimized + } + return found; +} + +/*! \reimp */ +void Q3TextEdit::polishEvent(QEvent*) +{ + // this will ensure that the last line is visible if text have + // been added to the widget before it is shown + if (d->optimMode) + scrollToBottom(); +} + +/*! + Sets the maximum number of lines a Q3TextEdit can hold in \c + Qt::LogText mode to \a limit. If \a limit is -1 (the default), this + signifies an unlimited number of lines. + + \warning Never use formatting tags that span more than one line + when the maximum log lines is set. When lines are removed from the + top of the buffer it could result in an unbalanced tag pair, i.e. + the left formatting tag is removed before the right one. + */ +void Q3TextEdit::setMaxLogLines(int limit) +{ + d->maxLogLines = limit; + if (d->maxLogLines < -1) + d->maxLogLines = -1; + if (d->maxLogLines == -1) + d->logOffset = 0; +} + +/*! + Returns the maximum number of lines Q3TextEdit can hold in \c + Qt::LogText mode. By default the number of lines is unlimited, which + is signified by a value of -1. + */ +int Q3TextEdit::maxLogLines() const +{ + return d->maxLogLines; +} + +/*! + Check if the number of lines in the buffer is limited, and uphold + that limit when appending new lines. + */ +void Q3TextEdit::optimCheckLimit(const QString& str) +{ + if (d->maxLogLines > -1 && d->maxLogLines <= d->od->numLines) { + // NB! Removing the top line in the buffer will potentially + // destroy the structure holding the formatting tags - if line + // spanning tags are used. + Q3TextEditOptimPrivate::Tag *t = d->od->tags, *tmp, *itr; + QList<Q3TextEditOptimPrivate::Tag *> lst; + while (t) { + t->line -= 1; + // unhook the ptr from the tag structure + if (((uint) LOGOFFSET(t->line) < (uint) d->logOffset && + (uint) LOGOFFSET(t->line) < (uint) LOGOFFSET(d->od->numLines) && + (uint) LOGOFFSET(d->od->numLines) > (uint) d->logOffset)) + { + if (t->prev) + t->prev->next = t->next; + if (t->next) + t->next->prev = t->prev; + if (d->od->tags == t) + d->od->tags = t->next; + if (d->od->lastTag == t) { + if (t->prev) + d->od->lastTag = t->prev; + else + d->od->lastTag = d->od->tags; + } + tmp = t; + t = t->next; + lst.append(tmp); + delete tmp; + } else { + t = t->next; + } + } + // Remove all references to the ptrs we just deleted + itr = d->od->tags; + while (itr) { + for (int i = 0; i < lst.size(); ++i) { + tmp = lst.at(i); + if (itr->parent == tmp) + itr->parent = 0; + if (itr->leftTag == tmp) + itr->leftTag = 0; + } + itr = itr->next; + } + // ...in the tag index as well + QMap<int, Q3TextEditOptimPrivate::Tag *>::Iterator idx; + if ((idx = d->od->tagIndex.find(d->logOffset)) != d->od->tagIndex.end()) + d->od->tagIndex.erase(idx); + + QMap<int,QString>::Iterator it; + if ((it = d->od->lines.find(d->logOffset)) != d->od->lines.end()) { + d->od->len -= (*it).length(); + d->od->lines.erase(it); + d->od->numLines--; + d->logOffset = LOGOFFSET(1); + } + } + d->od->len += str.length(); + d->od->lines[LOGOFFSET(d->od->numLines++)] = str; +} + +#endif // QT_TEXTEDIT_OPTIMIZATION + +/*! + \property Q3TextEdit::autoFormatting + \brief the enabled set of auto formatting features + + The value can be any combination of the values in the \c + AutoFormattingFlag enum. The default is \c AutoAll. Choose \c AutoNone + to disable all automatic formatting. + + Currently, the only automatic formatting feature provided is \c + AutoBulletList; future versions of Qt may offer more. +*/ + +void Q3TextEdit::setAutoFormatting(AutoFormatting features) +{ + d->autoFormatting = features; +} + +Q3TextEdit::AutoFormatting Q3TextEdit::autoFormatting() const +{ + return d->autoFormatting; +} + +/*! + Returns the QSyntaxHighlighter set on this Q3TextEdit. 0 is + returned if no syntax highlighter is set. + */ +Q3SyntaxHighlighter * Q3TextEdit::syntaxHighlighter() const +{ + if (document()->preProcessor()) + return ((Q3SyntaxHighlighterInternal *) document()->preProcessor())->highlighter; + else + return 0; +} + +QT_END_NAMESPACE + +#endif //QT_NO_TEXTEDIT diff --git a/src/qt3support/text/q3textedit.h b/src/qt3support/text/q3textedit.h new file mode 100644 index 0000000..fae22b0 --- /dev/null +++ b/src/qt3support/text/q3textedit.h @@ -0,0 +1,613 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3TEXTEDIT_H +#define Q3TEXTEDIT_H + +#include <Qt3Support/q3scrollview.h> +#include <Qt3Support/q3stylesheet.h> +#include <Qt3Support/q3mimefactory.h> +#include <QtCore/qmap.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3SupportLight) + +#ifndef QT_NO_TEXTEDIT +// uncomment below to enable optimization mode - also uncomment the +// optimDoAutoScroll() private slot since moc ignores #ifdefs.. +#define QT_TEXTEDIT_OPTIMIZATION + +class QPainter; +class Q3TextDocument; +class Q3TextCursor; +class QKeyEvent; +class QResizeEvent; +class QMouseEvent; +class QTimer; +class Q3TextString; +class QTextCommand; +class Q3TextParagraph; +class Q3TextFormat; +class QFont; +class QColor; +class Q3TextEdit; +class QTextBrowser; +class Q3TextString; +struct QUndoRedoInfoPrivate; +class Q3PopupMenu; +class Q3TextEditPrivate; +class Q3SyntaxHighlighter; +class Q3TextDrag; + +#ifdef QT_TEXTEDIT_OPTIMIZATION +class Q3TextEditOptimPrivate +{ +public: + // Note: no left-tag has any value for leftTag or parent, and + // no right-tag has any formatting flags set. + enum TagType { Color = 0, Format = 1 }; + struct Tag { + TagType type:2; + bool bold:1; + bool italic:1; + bool underline:1; + int line; + int index; + Tag * leftTag; // ptr to left-tag in a left-right tag pair + Tag * parent; // ptr to parent left-tag in a nested tag + Tag * prev; + Tag * next; + QString tag; + }; + Q3TextEditOptimPrivate() + { + len = numLines = maxLineWidth = 0; + selStart.line = selStart.index = -1; + selEnd.line = selEnd.index = -1; + search.line = search.index = 0; + tags = lastTag = 0; + } + void clearTags() + { + Tag * itr = tags; + while (tags) { + itr = tags; + tags = tags->next; + delete itr; + } + tags = lastTag = 0; + tagIndex.clear(); + } + ~Q3TextEditOptimPrivate() + { + clearTags(); + } + int len; + int numLines; + int maxLineWidth; + struct Selection { + int line; + int index; + }; + Selection selStart, selEnd, search; + Tag * tags, * lastTag; + QMap<int, QString> lines; + QMap<int, Tag *> tagIndex; +}; +#endif + +class Q_COMPAT_EXPORT Q3TextEdit : public Q3ScrollView +{ + friend class Q3TextBrowser; + friend class Q3SyntaxHighlighter; + + Q_OBJECT + Q_ENUMS(WordWrap WrapPolicy) + Q_FLAGS(AutoFormattingFlag) + Q_PROPERTY(Qt::TextFormat textFormat READ textFormat WRITE setTextFormat) + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QBrush paper READ paper WRITE setPaper) + Q_PROPERTY(bool linkUnderline READ linkUnderline WRITE setLinkUnderline) + Q_PROPERTY(QString documentTitle READ documentTitle) + Q_PROPERTY(int length READ length) + Q_PROPERTY(WordWrap wordWrap READ wordWrap WRITE setWordWrap) + Q_PROPERTY(int wrapColumnOrWidth READ wrapColumnOrWidth WRITE setWrapColumnOrWidth) + Q_PROPERTY(WrapPolicy wrapPolicy READ wrapPolicy WRITE setWrapPolicy) + Q_PROPERTY(bool hasSelectedText READ hasSelectedText) + Q_PROPERTY(QString selectedText READ selectedText) + Q_PROPERTY(int undoDepth READ undoDepth WRITE setUndoDepth) + Q_PROPERTY(bool overwriteMode READ isOverwriteMode WRITE setOverwriteMode) + Q_PROPERTY(bool modified READ isModified WRITE setModified DESIGNABLE false) + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) + Q_PROPERTY(bool undoRedoEnabled READ isUndoRedoEnabled WRITE setUndoRedoEnabled) + Q_PROPERTY(int tabStopWidth READ tabStopWidth WRITE setTabStopWidth) + Q_PROPERTY(bool tabChangesFocus READ tabChangesFocus WRITE setTabChangesFocus) + Q_PROPERTY(AutoFormattingFlag autoFormatting READ autoFormatting WRITE setAutoFormatting) + +public: + enum WordWrap { + NoWrap, + WidgetWidth, + FixedPixelWidth, + FixedColumnWidth + }; + + enum WrapPolicy { + AtWordBoundary, + AtWhiteSpace = AtWordBoundary, // AtWhiteSpace is deprecated + Anywhere, + AtWordOrDocumentBoundary + }; + + enum AutoFormattingFlag { + AutoNone = 0, + AutoBulletList = 0x00000001, + AutoAll = 0xffffffff + }; + + Q_DECLARE_FLAGS(AutoFormatting, AutoFormattingFlag) + + enum KeyboardAction { + ActionBackspace, + ActionDelete, + ActionReturn, + ActionKill, + ActionWordBackspace, + ActionWordDelete + }; + + enum CursorAction { + MoveBackward, + MoveForward, + MoveWordBackward, + MoveWordForward, + MoveUp, + MoveDown, + MoveLineStart, + MoveLineEnd, + MoveHome, + MoveEnd, + MovePgUp, + MovePgDown + }; + + enum VerticalAlignment { + AlignNormal, + AlignSuperScript, + AlignSubScript + }; + + enum TextInsertionFlags { + RedoIndentation = 0x0001, + CheckNewLines = 0x0002, + RemoveSelected = 0x0004 + }; + + Q3TextEdit(const QString& text, const QString& context = QString(), + QWidget* parent=0, const char* name=0); + Q3TextEdit(QWidget* parent=0, const char* name=0); + virtual ~Q3TextEdit(); + + QString text() const; + QString text(int para) const; + Qt::TextFormat textFormat() const; + QString context() const; + QString documentTitle() const; + + void getSelection(int *paraFrom, int *indexFrom, + int *paraTo, int *indexTo, int selNum = 0) const; + virtual bool find(const QString &expr, bool cs, bool wo, bool forward = true, + int *para = 0, int *index = 0); + + int paragraphs() const; + int lines() const; + int linesOfParagraph(int para) const; + int lineOfChar(int para, int chr); + int length() const; + QRect paragraphRect(int para) const; + int paragraphAt(const QPoint &pos) const; + int charAt(const QPoint &pos, int *para) const; + int paragraphLength(int para) const; + + Q3StyleSheet* styleSheet() const; +#ifndef QT_NO_MIME + Q3MimeSourceFactory* mimeSourceFactory() const; +#endif + QBrush paper() const; + bool linkUnderline() const; + + int heightForWidth(int w) const; + + bool hasSelectedText() const; + QString selectedText() const; + bool isUndoAvailable() const; + bool isRedoAvailable() const; + + WordWrap wordWrap() const; + int wrapColumnOrWidth() const; + WrapPolicy wrapPolicy() const; + + int tabStopWidth() const; + + QString anchorAt(const QPoint& pos, Qt::AnchorAttribute a = Qt::AnchorHref); + + QSize sizeHint() const; + + bool isReadOnly() const { return readonly; } + + void getCursorPosition(int *parag, int *index) const; + + bool isModified() const; + bool italic() const; + bool bold() const; + bool underline() const; + QString family() const; + int pointSize() const; + QColor color() const; + QFont font() const; + QFont currentFont() const; + int alignment() const; + VerticalAlignment verticalAlignment() const; + int undoDepth() const; + + // do not use, will go away + virtual bool getFormat(int para, int index, QFont *font, QColor *color, VerticalAlignment *verticalAlignment); + // do not use, will go away + virtual bool getParagraphFormat(int para, QFont *font, QColor *color, + VerticalAlignment *verticalAlignment, int *alignment, + Q3StyleSheetItem::DisplayMode *displayMode, + Q3StyleSheetItem::ListStyle *listStyle, + int *listDepth); + + + bool isOverwriteMode() const { return overWrite; } + QColor paragraphBackgroundColor(int para) const; + + bool isUndoRedoEnabled() const; + bool eventFilter(QObject *o, QEvent *e); + bool tabChangesFocus() const; + + void setAutoFormatting(AutoFormatting); + AutoFormatting autoFormatting() const; + Q3SyntaxHighlighter *syntaxHighlighter() const; + +public Q_SLOTS: +#ifndef QT_NO_MIME + virtual void setMimeSourceFactory(Q3MimeSourceFactory* factory); +#endif + virtual void setStyleSheet(Q3StyleSheet* styleSheet); + virtual void scrollToAnchor(const QString& name); + virtual void setPaper(const QBrush& pap); + virtual void setLinkUnderline(bool); + + virtual void setWordWrap(Q3TextEdit::WordWrap mode); + virtual void setWrapColumnOrWidth(int); + virtual void setWrapPolicy(Q3TextEdit::WrapPolicy policy); + + virtual void copy(); + virtual void append(const QString& text); + + void setText(const QString &txt) { setText(txt, QString()); } + virtual void setText(const QString &txt, const QString &context); + virtual void setTextFormat(Qt::TextFormat f); + + virtual void selectAll(bool select = true); + virtual void setTabStopWidth(int ts); + virtual void zoomIn(int range); + virtual void zoomIn() { zoomIn(1); } + virtual void zoomOut(int range); + virtual void zoomOut() { zoomOut(1); } + virtual void zoomTo(int size); + + virtual void sync(); + virtual void setReadOnly(bool b); + + virtual void undo(); + virtual void redo(); + virtual void cut(); + virtual void paste(); +#ifndef QT_NO_CLIPBOARD + virtual void pasteSubType(const QByteArray &subtype); +#endif + virtual void clear(); + virtual void del(); + virtual void indent(); + virtual void setItalic(bool b); + virtual void setBold(bool b); + virtual void setUnderline(bool b); + virtual void setFamily(const QString &f); + virtual void setPointSize(int s); + virtual void setColor(const QColor &c); + virtual void setVerticalAlignment(Q3TextEdit::VerticalAlignment a); + virtual void setAlignment(int a); + + // do not use, will go away + virtual void setParagType(Q3StyleSheetItem::DisplayMode dm, Q3StyleSheetItem::ListStyle listStyle); + + virtual void setCursorPosition(int parag, int index); + virtual void setSelection(int parag_from, int index_from, int parag_to, int index_to, int selNum = 0); + virtual void setSelectionAttributes(int selNum, const QColor &back, bool invertText); + virtual void setModified(bool m); + virtual void resetFormat(); + virtual void setUndoDepth(int d); + virtual void setFormat(Q3TextFormat *f, int flags); + virtual void ensureCursorVisible(); + virtual void placeCursor(const QPoint &pos, Q3TextCursor *c = 0); + virtual void moveCursor(Q3TextEdit::CursorAction action, bool select); + virtual void doKeyboardAction(Q3TextEdit::KeyboardAction action); + virtual void removeSelectedText(int selNum = 0); + virtual void removeSelection(int selNum = 0); + virtual void setCurrentFont(const QFont &f); + virtual void setOverwriteMode(bool b) { overWrite = b; } + + virtual void scrollToBottom(); + + virtual void insert(const QString &text, uint insertionFlags = CheckNewLines | RemoveSelected); + + // obsolete + virtual void insert(const QString &text, bool, bool = true, bool = true); + + virtual void insertAt(const QString &text, int para, int index); + virtual void removeParagraph(int para); + virtual void insertParagraph(const QString &text, int para); + + virtual void setParagraphBackgroundColor(int para, const QColor &bg); + virtual void clearParagraphBackground(int para); + + virtual void setUndoRedoEnabled(bool b); + virtual void setTabChangesFocus(bool b); + +#ifdef QT_TEXTEDIT_OPTIMIZATION + void polishEvent(QEvent*); + void setMaxLogLines(int numLines); + int maxLogLines() const; +#endif + +Q_SIGNALS: + void textChanged(); + void selectionChanged(); + void copyAvailable(bool); + void undoAvailable(bool yes); + void redoAvailable(bool yes); + void currentFontChanged(const QFont &f); + void currentColorChanged(const QColor &c); + void currentAlignmentChanged(int a); + void currentVerticalAlignmentChanged(Q3TextEdit::VerticalAlignment a); + void cursorPositionChanged(Q3TextCursor *c); + void cursorPositionChanged(int para, int pos); + void returnPressed(); + void modificationChanged(bool m); + void clicked(int parag, int index); + void doubleClicked(int parag, int index); + +protected: + void repaintChanged(); + void updateStyles(); + void drawContents(QPainter *p, int cx, int cy, int cw, int ch); + bool event(QEvent *e); + void changeEvent(QEvent *); + void keyPressEvent(QKeyEvent *e); + void resizeEvent(QResizeEvent *e); + void viewportResizeEvent(QResizeEvent*); + void contentsMousePressEvent(QMouseEvent *e); + void contentsMouseMoveEvent(QMouseEvent *e); + void contentsMouseReleaseEvent(QMouseEvent *e); + void contentsMouseDoubleClickEvent(QMouseEvent *e); +#ifndef QT_NO_WHEELEVENT + void contentsWheelEvent(QWheelEvent *e); +#endif + void inputMethodEvent(QInputMethodEvent *); +#ifndef QT_NO_DRAGANDDROP + void contentsDragEnterEvent(QDragEnterEvent *e); + void contentsDragMoveEvent(QDragMoveEvent *e); + void contentsDragLeaveEvent(QDragLeaveEvent *e); + void contentsDropEvent(QDropEvent *e); +#endif + void contentsContextMenuEvent(QContextMenuEvent *e); + bool focusNextPrevChild(bool next); + Q3TextDocument *document() const; + Q3TextCursor *textCursor() const; + void setDocument(Q3TextDocument *doc); + virtual Q3PopupMenu *createPopupMenu(const QPoint& pos); + virtual Q3PopupMenu *createPopupMenu(); + void drawCursor(bool visible); + +protected Q_SLOTS: + virtual void doChangeInterval(); + virtual void sliderReleased(); + +private Q_SLOTS: + void formatMore(); + void doResize(); + void autoScrollTimerDone(); + void blinkCursor(); + void setModified(); + void startDrag(); + void documentWidthChanged(int w); + void clipboardChanged(); + +private: + struct Q_COMPAT_EXPORT UndoRedoInfo { + enum Type { Invalid, Insert, Delete, Backspace, Return, RemoveSelected, Format, Style, IME }; + + UndoRedoInfo(Q3TextDocument *dc); + ~UndoRedoInfo(); + void clear(); + bool valid() const; + + QUndoRedoInfoPrivate *d; + int id; + int index; + int eid; + int eindex; + Q3TextFormat *format; + int flags; + Type type; + Q3TextDocument *doc; + QByteArray styleInformation; + }; + +private: + void updateCursor(const QPoint & pos); + void handleMouseMove(const QPoint& pos); + void drawContents(QPainter *); + virtual bool linksEnabled() const { return false; } + void init(); + void checkUndoRedoInfo(UndoRedoInfo::Type t); + void updateCurrentFormat(); + bool handleReadOnlyKeyEvent(QKeyEvent *e); + void makeParagVisible(Q3TextParagraph *p); + void normalCopy(); + void copyToClipboard(); +#ifndef QT_NO_MIME + QByteArray pickSpecial(QMimeSource* ms, bool always_ask, const QPoint&); + Q3TextDrag *dragObject(QWidget *parent = 0) const; +#endif +#ifndef QT_NO_MIMECLIPBOARD + void pasteSpecial(const QPoint&); +#endif + void setFontInternal(const QFont &f); + + virtual void emitHighlighted(const QString &) {} + virtual void emitLinkClicked(const QString &) {} + + void readFormats(Q3TextCursor &c1, Q3TextCursor &c2, Q3TextString &text, bool fillStyles = false); + void clearUndoRedo(); + void paintDocument(bool drawAll, QPainter *p, int cx = -1, int cy = -1, int cw = -1, int ch = -1); + void moveCursor(CursorAction action); + void ensureFormatted(Q3TextParagraph *p); + void placeCursor(const QPoint &pos, Q3TextCursor *c, bool link); + QVariant inputMethodQuery(Qt::InputMethodQuery query) const; + +#ifdef QT_TEXTEDIT_OPTIMIZATION + bool checkOptimMode(); + QString optimText() const; + void optimSetText(const QString &str); + void optimAppend(const QString &str); + void optimInsert(const QString &str, int line, int index); + void optimDrawContents(QPainter * p, int cx, int cy, int cw, int ch); + void optimMousePressEvent(QMouseEvent * e); + void optimMouseReleaseEvent(QMouseEvent * e); + void optimMouseMoveEvent(QMouseEvent * e); + int optimCharIndex(const QString &str, int mx) const; + void optimSelectAll(); + void optimRemoveSelection(); + void optimSetSelection(int startLine, int startIdx, int endLine, + int endIdx); + bool optimHasSelection() const; + QString optimSelectedText() const; + bool optimFind(const QString & str, bool, bool, bool, int *, int *); + void optimParseTags(QString * str, int lineNo = -1, int indexOffset = 0); + Q3TextEditOptimPrivate::Tag * optimPreviousLeftTag(int line); + void optimSetTextFormat(Q3TextDocument *, Q3TextCursor *, Q3TextFormat * f, + int, int, Q3TextEditOptimPrivate::Tag * t); + Q3TextEditOptimPrivate::Tag * optimAppendTag(int index, const QString & tag); + Q3TextEditOptimPrivate::Tag * optimInsertTag(int line, int index, const QString & tag); + void optimCheckLimit(const QString& str); + bool optimHasBoldMetrics(int line); + +private Q_SLOTS: + void optimDoAutoScroll(); +#endif // QT_TEXTEDIT_OPTIMIZATION + +private: +#ifndef QT_NO_CLIPBOARD + void pasteSubType(const QByteArray &subtype, QMimeSource *m); +#endif + +private: + Q_DISABLE_COPY(Q3TextEdit) + + Q3TextDocument *doc; + Q3TextCursor *cursor; + QTimer *formatTimer, *scrollTimer, *changeIntervalTimer, *blinkTimer, *dragStartTimer; + Q3TextParagraph *lastFormatted; + int interval; + UndoRedoInfo undoRedoInfo; + Q3TextFormat *currentFormat; + int currentAlignment; + QPoint oldMousePos, mousePos; + QPoint dragStartPos; + QString onLink; + WordWrap wrapMode; + WrapPolicy wPolicy; + int wrapWidth; + QString pressedLink; + Q3TextEditPrivate *d; + bool inDoubleClick : 1; + bool mousePressed : 1; + bool cursorVisible : 1; + bool blinkCursorVisible : 1; + bool modified : 1; + bool mightStartDrag : 1; + bool inDnD : 1; + bool readonly : 1; + bool undoEnabled : 1; + bool overWrite : 1; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(Q3TextEdit::AutoFormatting) + +inline Q3TextDocument *Q3TextEdit::document() const +{ + return doc; +} + +inline Q3TextCursor *Q3TextEdit::textCursor() const +{ + return cursor; +} + +inline void Q3TextEdit::setCurrentFont(const QFont &f) +{ + Q3TextEdit::setFontInternal(f); +} + +#endif // QT_NO_TEXTEDIT + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3TEXTEDIT_H diff --git a/src/qt3support/text/q3textstream.cpp b/src/qt3support/text/q3textstream.cpp new file mode 100644 index 0000000..15fa6b0 --- /dev/null +++ b/src/qt3support/text/q3textstream.cpp @@ -0,0 +1,2436 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3textstream.h" +#include <qdebug.h> + +#ifndef QT_NO_TEXTSTREAM +#include "qtextcodec.h" +#include "qregexp.h" +#include "qbuffer.h" +#include "qfile.h" +#include "q3cstring.h" +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> +#ifndef Q_OS_WINCE +#include <locale.h> +#endif + +#if defined(Q_OS_WIN32) +#include "qt_windows.h" +#endif + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_TEXTCODEC +static void resetCodecConverterState(QTextCodec::ConverterState *state) { + state->flags = QTextCodec::DefaultConversion; + state->remainingChars = state->invalidChars = + state->state_data[0] = state->state_data[1] = state->state_data[2] = 0; + if (state->d) qFree(state->d); + state->d = 0; +} +#endif + +/*! + \class Q3TextStream + \compat + \reentrant + \brief The Q3TextStream class provides basic functions for reading + and writing text using a QIODevice. + + The text stream class has a functional interface that is very + similar to that of the standard C++ iostream class. + + Qt provides several global functions similar to the ones in iostream: + \table + \header \i Function \i Meaning + \row \i bin \i sets the Q3TextStream to read/write binary numbers + \row \i oct \i sets the Q3TextStream to read/write octal numbers + \row \i dec \i sets the Q3TextStream to read/write decimal numbers + \row \i hex \i sets the Q3TextStream to read/write hexadecimal numbers + \row \i endl \i forces a line break + \row \i flush \i forces the QIODevice to flush any buffered data + \row \i ws \i eats any available whitespace (on input) + \row \i reset \i resets the Q3TextStream to its default mode (see reset()) + \row \i qSetW(int) \i sets the \link width() field width \endlink + to the given argument + \row \i qSetFill(int) \i sets the \link fill() fill character + \endlink to the given argument + \row \i qSetPrecision(int) \i sets the \link precision() precision + \endlink to the given argument + \endtable + + \warning By default Q3TextStream will automatically detect whether + integers in the stream are in decimal, octal, hexadecimal or + binary format when reading from the stream. In particular, a + leading '0' signifies octal, i.e. the sequence "0100" will be + interpreted as 64. + + The Q3TextStream class reads and writes text; it is not appropriate + for dealing with binary data (but QDataStream is). + + By default, output of Unicode text (i.e. QString) is done using + the local 8-bit encoding. This can be changed using the + setEncoding() method. For input, the Q3TextStream will auto-detect + standard Unicode "byte order marked" text files; otherwise the + local 8-bit encoding is used. + + The QIODevice is set in the constructor, or later using + setDevice(). If the end of the input is reached atEnd() returns + TRUE. Data can be read into variables of the appropriate type + using the operator>>() overloads, or read in its entirety into a + single string using read(), or read a line at a time using + readLine(). Whitespace can be skipped over using skipWhiteSpace(). + You can set flags for the stream using flags() or setf(). The + stream also supports width(), precision() and fill(); use reset() + to reset the defaults. + + \sa QDataStream +*/ + +/*! + \enum Q3TextStream::Encoding + + \value Locale + \value Latin1 + \value Unicode + \value UnicodeNetworkOrder + \value UnicodeReverse + \value RawUnicode + \value UnicodeUTF8 + + See setEncoding() for an explanation of the encodings. +*/ + +/* + \class QTSManip + \internal +*/ + +#if defined(QT_CHECK_STATE) +#undef CHECK_STREAM_PRECOND +#define CHECK_STREAM_PRECOND if ( !dev ) { \ + qWarning( "Q3TextStream: No device" ); \ + return *this; } +#else +#define CHECK_STREAM_PRECOND +#endif + + +#define I_SHORT 0x0010 +#define I_INT 0x0020 +#define I_LONG 0x0030 +#define I_TYPE_MASK 0x00f0 + +#define I_BASE_2 Q3TextStream::bin +#define I_BASE_8 Q3TextStream::oct +#define I_BASE_10 Q3TextStream::dec +#define I_BASE_16 Q3TextStream::hex +#define I_BASE_MASK (Q3TextStream::bin | Q3TextStream::oct | Q3TextStream::dec | Q3TextStream::hex) + +#define I_SIGNED 0x0100 +#define I_UNSIGNED 0x0200 +#define I_SIGN_MASK 0x0f00 + + +static const QChar QEOF = QChar((ushort)0xffff); //guaranteed not to be a character. +static const uint getline_buf_size = 256; // bufsize used by ts_getline() + +const int Q3TextStream::basefield = I_BASE_MASK; +const int Q3TextStream::adjustfield = ( Q3TextStream::left | + Q3TextStream::right | + Q3TextStream::internal ); +const int Q3TextStream::floatfield = ( Q3TextStream::scientific | + Q3TextStream::fixed ); + + +class Q3TextStreamPrivate { +public: +#ifndef QT_NO_TEXTCODEC + Q3TextStreamPrivate() + : sourceType( NotSet ) { } + ~Q3TextStreamPrivate() { + } +#else + Q3TextStreamPrivate() : sourceType( NotSet ) { } + ~Q3TextStreamPrivate() { } +#endif + QString ungetcBuf; + + enum SourceType { NotSet, IODevice, String, ByteArray, File }; + SourceType sourceType; +}; + + +// skips whitespace and returns the first non-whitespace character +QChar Q3TextStream::eat_ws() +{ + QChar c; + do { c = ts_getc(); } while ( c != QEOF && ts_isspace(c) ); + return c; +} + +void Q3TextStream::init() +{ + // ### ungetcBuf = QEOF; + dev = 0; + owndev = FALSE; + mapper = 0; +#ifndef QT_NO_TEXTCODEC + resetCodecConverterState(&mapperReadState); + resetCodecConverterState(&mapperWriteState); +#endif + d = new Q3TextStreamPrivate; + doUnicodeHeader = TRUE; // autodetect + latin1 = TRUE; // should use locale? + internalOrder = QChar::networkOrdered(); + networkOrder = TRUE; +} + +/*! + Constructs a data stream that has no IO device. +*/ + +Q3TextStream::Q3TextStream() +{ + init(); + setEncoding( Locale ); + reset(); + d->sourceType = Q3TextStreamPrivate::NotSet; +} + +/*! + Constructs a text stream that uses the IO device \a iod. +*/ + +Q3TextStream::Q3TextStream( QIODevice *iod ) +{ + init(); + setEncoding( Locale ); + dev = iod; + reset(); + d->sourceType = Q3TextStreamPrivate::IODevice; +} + +// TODO: use special-case handling of this case in Q3TextStream, and +// simplify this class to only deal with QChar or QString data. +class QStringBuffer : public QIODevice { +public: + QStringBuffer( QString* str ); + ~QStringBuffer(); + bool open( OpenMode m ); + void close(); + qint64 size() const; + +protected: + qint64 readData( char *p, qint64 len ); + qint64 writeData( const char *p, qint64 len ); + + QString* s; + +private: + QStringBuffer( const QStringBuffer & ); + QStringBuffer &operator=( const QStringBuffer & ); +}; + + +QStringBuffer::QStringBuffer( QString* str ) +{ + s = str; +} + +QStringBuffer::~QStringBuffer() +{ +} + + +bool QStringBuffer::open( OpenMode m ) +{ + if ( !s ) { +#if defined(QT_CHECK_STATE) + qWarning( "QStringBuffer::open: No string" ); +#endif + return FALSE; + } + if ( isOpen() ) { +#if defined(QT_CHECK_STATE) + qWarning( "QStringBuffer::open: Buffer already open" ); +#endif + return FALSE; + } + setOpenMode( m ); + if ( m & QIODevice::Truncate ) + s->truncate( 0 ); + + if ( m & QIODevice::Append ) { + seek(s->length()*sizeof(QChar)); + } else { + seek(0); + } + return TRUE; +} + +void QStringBuffer::close() +{ + if ( isOpen() ) { + seek(0); + QIODevice::close(); + } +} + +qint64 QStringBuffer::size() const +{ + return s ? s->length()*sizeof(QChar) : 0; +} + +qint64 QStringBuffer::readData( char *p, qint64 len ) +{ +#if defined(QT_CHECK_STATE) + Q_CHECK_PTR( p ); + if ( !isOpen() ) { + qWarning( "QStringBuffer::readBlock: Buffer not open" ); + return qint64(-1); + } + if ( !isReadable() ) { + qWarning( "QStringBuffer::readBlock: Read operation not permitted" ); + return qint64(-1); + } +#endif + if ( pos() + len > qint64(s->length()*sizeof(QChar)) ) { + // overflow + if ( pos() >= qint64(s->length()*sizeof(QChar)) ) { + return -1; + } else { + len = s->length()*2 - pos(); + } + } + memcpy( p, ((const char*)(s->unicode()))+pos(), len ); + return len; +} + +qint64 QStringBuffer::writeData( const char *p, qint64 len ) +{ +#if defined(QT_CHECK_NULL) + if ( p == 0 && len != 0 ) + qWarning( "QStringBuffer::writeBlock: Null pointer error" ); +#endif +#if defined(QT_CHECK_STATE) + if ( !isOpen() ) { + qWarning( "QStringBuffer::writeBlock: Buffer not open" ); + return -1; + } + if ( !isWritable() ) { + qWarning( "QStringBuffer::writeBlock: Write operation not permitted" ); + return -1; + } + if ( pos()&1 ) { + qWarning( "QStringBuffer::writeBlock: non-even index - non Unicode" ); + return -1; + } + if ( len&1 ) { + qWarning( "QStringBuffer::writeBlock: non-even length - non Unicode" ); + return -1; + } +#endif + s->replace(pos()/2, len/2, (QChar*)p, len/2); + return len; +} + +/*! + Constructs a text stream that operates on the Unicode QString, \a + str, through an internal device. The \a filemode argument is + passed to the device's open() function; see \l{QIODevice::mode()}. + + If you set an encoding or codec with setEncoding() or setCodec(), + this setting is ignored for text streams that operate on QString. + + Example: + \snippet doc/src/snippets/code/src_qt3support_text_q3textstream.cpp 0 + + Writing data to the text stream will modify the contents of the + string. The string will be expanded when data is written beyond + the end of the string. Note that the string will not be truncated: + \snippet doc/src/snippets/code/src_qt3support_text_q3textstream.cpp 1 + + Note that because QString is Unicode, you should not use + readRawBytes() or writeRawBytes() on such a stream. +*/ + +Q3TextStream::Q3TextStream( QString* str, int filemode ) +{ + // TODO: optimize for this case as it becomes more common + // (see QStringBuffer above) + init(); + dev = new QStringBuffer( str ); + ((QStringBuffer *)dev)->open( QIODevice::OpenMode(filemode) ); + owndev = TRUE; + setEncoding(RawUnicode); + reset(); + d->sourceType = Q3TextStreamPrivate::String; +} + +/*! \obsolete + + This constructor is equivalent to the constructor taking a QString* + parameter. +*/ + +Q3TextStream::Q3TextStream( QString& str, int filemode ) +{ + init(); + dev = new QStringBuffer( &str ); + ((QStringBuffer *)dev)->open( QIODevice::OpenMode(filemode) ); + owndev = TRUE; + setEncoding(RawUnicode); + reset(); + d->sourceType = Q3TextStreamPrivate::String; +} + +/*! + Constructs a text stream that operates on the byte array, \a a, + through an internal QBuffer device. The \a mode argument is passed + to the device's open() function; see \l{QIODevice::mode()}. + + Example: + \snippet doc/src/snippets/code/src_qt3support_text_q3textstream.cpp 2 + + Writing data to the text stream will modify the contents of the + array. The array will be expanded when data is written beyond the + end of the string. + + Same example, using a QBuffer: + \snippet doc/src/snippets/code/src_qt3support_text_q3textstream.cpp 3 +*/ + +Q3TextStream::Q3TextStream( QByteArray &a, int mode ) +{ + init(); + QBuffer *buffer = new QBuffer; + buffer->setBuffer( &a ); + buffer->open( QIODevice::OpenMode(mode) ); + dev = buffer; + owndev = TRUE; + setEncoding( Latin1 ); //### Locale??? + reset(); + d->sourceType = Q3TextStreamPrivate::ByteArray; +} + +/*! + Constructs a text stream that operates on an existing file handle + \a fh through an internal QFile device. The \a mode argument is + passed to the device's open() function; see \l{QIODevice::mode()}. + + Note that if you create a Q3TextStream \c cout or another name that + is also used for another variable of a different type, some + linkers may confuse the two variables, which will often cause + crashes. +*/ + +Q3TextStream::Q3TextStream( FILE *fh, int mode ) +{ + init(); + setEncoding( Locale ); //### + dev = new QFile; + ((QFile *)dev)->open( QIODevice::OpenMode(mode), fh ); + owndev = TRUE; + reset(); + d->sourceType = Q3TextStreamPrivate::File; +} + +/*! + Destroys the text stream. + + The destructor does not affect the current IO device. +*/ + +Q3TextStream::~Q3TextStream() +{ + if ( owndev ) + delete dev; + delete d; +} + +/*! + \since 4.2 + + Positions the read pointer at the first non-whitespace character. +*/ +void Q3TextStream::skipWhiteSpace() +{ + ts_ungetc( eat_ws() ); +} + + +/*! + Tries to read \a len characters from the stream and stores them in + \a buf. Returns the number of characters really read. + + \warning There will no QEOF appended if the read reaches the end + of the file. EOF is reached when the return value does not equal + \a len. +*/ +uint Q3TextStream::ts_getbuf( QChar* buf, uint len ) +{ + if ( len < 1 ) + return 0; + + uint rnum = 0; // the number of QChars really read + + if ( d && d->ungetcBuf.length() ) { + while ( rnum < len && rnum < uint(d->ungetcBuf.length()) ) { + *buf = d->ungetcBuf.constref( rnum ); + buf++; + rnum++; + } + d->ungetcBuf = d->ungetcBuf.mid( rnum ); + if ( rnum >= len ) + return rnum; + } + + // we use dev->ungetch() for one of the bytes of the unicode + // byte-order mark, but a local unget hack for the other byte: + int ungetHack = EOF; + + if ( doUnicodeHeader ) { + doUnicodeHeader = FALSE; // only at the top + int c1 = dev->getch(); + if ( c1 == EOF ) + return rnum; + int c2 = dev->getch(); + if ( c1 == 0xfe && c2 == 0xff ) { + mapper = 0; + latin1 = FALSE; + internalOrder = QChar::networkOrdered(); + networkOrder = TRUE; + } else if ( c1 == 0xff && c2 == 0xfe ) { + mapper = 0; + latin1 = FALSE; + internalOrder = !QChar::networkOrdered(); + networkOrder = FALSE; + } else { + if ( c2 != EOF ) { + dev->ungetch( c2 ); + ungetHack = c1; + } else { + /* + A small bug might hide here. If only the first byte + of a file has made it so far, and that first byte + is half of the byte-order mark, then the utfness + will not be detected. + */ + dev->ungetch( c1 ); + } + } + } + +#ifndef QT_NO_TEXTCODEC + if ( mapper ) { + bool shortRead = FALSE; + while( rnum < len ) { + QString s; + bool readBlock = !( len == 1+rnum ); + for (;;) { + // for efficiency: normally read a whole block + if ( readBlock ) { + // guess buffersize; this may be wrong (too small or too + // big). But we can handle this (either iterate reading + // or use ungetcBuf). + // Note that this might cause problems for codecs where + // one byte can result in >1 Unicode Characters if bytes + // are written to the stream in the meantime (loss of + // synchronicity). + uint rlen = len - rnum; + char *cbuf = new char[ rlen ]; + if ( ungetHack != EOF ) { + rlen = 1+dev->readBlock( cbuf+1, rlen-1 ); + cbuf[0] = (char)ungetHack; + ungetHack = EOF; + } else { + rlen = dev->readBlock( cbuf, rlen ); + } + s += mapper->toUnicode( cbuf, rlen, &mapperWriteState ); + delete[] cbuf; + // use buffered reading only for the first time, because we + // have to get the stream synchronous again (this is easier + // with single character reading) + readBlock = FALSE; + } + // get stream (and codec) in sync + int c; + if ( ungetHack == EOF ) { + c = dev->getch(); + } else { + c = ungetHack; + ungetHack = EOF; + } + if ( c == EOF ) { + shortRead = TRUE; + break; + } + char b = c; + uint lengthBefore = s.length(); + s += mapper->toUnicode( &b, 1, &mapperWriteState ); + + if ( uint(s.length()) > lengthBefore ) + break; // it seems we are in sync now + } + uint i = 0; + uint end = QMIN( len-rnum, uint(s.length()) ); + while( i < end ) { + *buf = s.constref(i++); + buf++; + } + rnum += end; + if ( uint(s.length()) > i ) { + // could be = but append is clearer + d->ungetcBuf.append( s.mid( i ) ); + } + if ( shortRead ) + return rnum; + } + } else +#endif + if ( latin1 ) { + if ( len == 1+rnum ) { + // use this method for one character because it is more efficient + // (arnt doubts whether it makes a difference, but lets it stand) + int c = (ungetHack == EOF) ? dev->getch() : ungetHack; + if ( c != EOF ) { + *buf = QLatin1Char((char)c); + buf++; + rnum++; + } + } else { + if ( ungetHack != EOF ) { + *buf = QLatin1Char((char)ungetHack); + buf++; + rnum++; + ungetHack = EOF; + } + char *cbuf = new char[len - rnum]; + while ( !dev->atEnd() && rnum < len ) { + uint rlen = len - rnum; + rlen = dev->readBlock( cbuf, rlen ); + char *it = cbuf; + char *end = cbuf + rlen; + while ( it < end ) { + *buf = QLatin1Char(*it); + buf++; + it++; + } + rnum += rlen; + } + delete[] cbuf; + } + } else { // UCS-2 or UTF-16 + if ( len == 1+rnum ) { + int c1 = (ungetHack == EOF) ? dev->getch() : ungetHack; + + + if ( c1 == EOF ) + return rnum; + int c2 = dev->getch(); + + + if ( c2 == EOF ) + return rnum; + + if ( networkOrder ) { + *buf = QChar( c2, c1 ); + } else { + *buf = QChar( c1, c2 ); + } + buf++; + rnum++; + } else { + char *cbuf = new char[ 2*( len - rnum ) ]; // for paranoids: overflow possible + while ( !dev->atEnd() && rnum < len ) { + uint rlen = 2 * ( len-rnum ); + if ( ungetHack != EOF ) { + rlen = 1+dev->readBlock( cbuf+1, rlen-1 ); + cbuf[0] = (char)ungetHack; + ungetHack = EOF; + } else { + rlen = dev->readBlock( cbuf, rlen ); + } + // We can't use an odd number of bytes, so put it back. But + // do it only if we are capable of reading more -- normally + // there should not be an odd number, but the file might be + // truncated or not in UTF-16... + if ( (rlen & 1) == 1 ) + if ( !dev->atEnd() ) + dev->ungetch( cbuf[--rlen] ); + uint i = 0; + if ( networkOrder ) { + while( i < rlen ) { + *buf = QChar( cbuf[i+1], cbuf[i] ); + buf++; + i+=2; + } + } else { + while( i < rlen ) { + *buf = QChar( cbuf[i], cbuf[i+1] ); + buf++; + i+=2; + } + } + rnum += i/2; + } + delete[] cbuf; + } + } + return rnum; +} + +/*! + Tries to read one line, but at most len characters from the stream + and stores them in \a buf. + + Returns the number of characters really read. Newlines are not + stripped. + + There will be a QEOF appended if the read reaches the end of file; + this is different to ts_getbuf(). + + This function works only if a newline (as byte) is also a newline + (as resulting character) since it uses QIODevice::readLine(). So + use it only for such codecs where this is true! + + This function is (almost) a no-op for UTF 16. Don't use it if + doUnicodeHeader is TRUE! +*/ +uint Q3TextStream::ts_getline( QChar* buf ) +{ + uint rnum=0; // the number of QChars really read + char cbuf[ getline_buf_size+1 ]; + + if ( d && d->ungetcBuf.length() ) { + while( rnum < getline_buf_size && rnum < uint(d->ungetcBuf.length()) ) { + buf[rnum] = d->ungetcBuf.constref(rnum); + rnum++; + } + d->ungetcBuf = d->ungetcBuf.mid( rnum ); + if ( rnum >= getline_buf_size ) + return rnum; + } + +#ifndef QT_NO_TEXTCODEC + if ( mapper ) { + QString s; + bool readBlock = TRUE; + for (;;) { + // for efficiency: try to read a line + if ( readBlock ) { + int rlen = getline_buf_size - rnum; + rlen = dev->readLine( cbuf, rlen+1 ); + if ( rlen == -1 ) + rlen = 0; + s += mapper->toUnicode( cbuf, rlen, &mapperWriteState ); + readBlock = FALSE; + } + if ( dev->atEnd() + || s.at( s.length()-1 ) == QLatin1Char('\n') + || s.at( s.length()-1 ) == QLatin1Char('\r') + ) { + break; + } else { + // get stream (and codec) in sync + int c; + c = dev->getch(); + if ( c == EOF ) { + break; + } + char b = c; + uint lengthBefore = s.length(); + s += mapper->toUnicode( &b, 1, &mapperWriteState ); + if ( uint(s.length()) > lengthBefore ) + break; // it seems we are in sync now + } + } + uint i = 0; + while( rnum < getline_buf_size && i < uint(s.length()) ) + buf[rnum++] = s.constref(i++); + if ( uint(s.length()) > i ) + // could be = but append is clearer + d->ungetcBuf.append( s.mid( i ) ); + if ( rnum < getline_buf_size && dev->atEnd() ) + buf[rnum++] = QEOF; + } else +#endif + if ( latin1 ) { + int rlen = getline_buf_size - rnum; + rlen = dev->readLine( cbuf, rlen+1 ); + if ( rlen == -1 ) + rlen = 0; + char *end = cbuf+rlen; + char *it = cbuf; + buf +=rnum; + while ( it != end ) { + buf->setCell( *(it++) ); + buf->setRow( 0 ); + buf++; + } + rnum += rlen; + if ( rnum < getline_buf_size && dev->atEnd() ) + buf[1] = QEOF; + } + return rnum; +} + + +/*! + Puts one character into the stream. +*/ +void Q3TextStream::ts_putc( QChar c ) +{ +#ifndef QT_NO_TEXTCODEC + if ( mapper ) { + int len = 1; + QString s = c; + Q3CString block = mapper->fromUnicode( s.data(), len );//, &mapperReadState ); + dev->writeBlock( block ); + } else +#endif + if ( latin1 ) { + if ( c.row() ) + dev->putch( '?' ); // unknown character + else + dev->putch( c.cell() ); + } else { + if ( doUnicodeHeader ) { + doUnicodeHeader = FALSE; + ts_putc( QChar::ByteOrderMark ); + } + if ( internalOrder ) { + // this case is needed by QStringBuffer + dev->writeBlock( (char*)&c, sizeof(QChar) ); + } else if ( networkOrder ) { + dev->putch( c.row() ); + dev->putch( c.cell() ); + } else { + dev->putch( c.cell() ); + dev->putch( c.row() ); + } + } +} + +/*! + Puts one character into the stream. +*/ +void Q3TextStream::ts_putc( int ch ) +{ + ts_putc( QChar((ushort)ch) ); +} + +bool Q3TextStream::ts_isdigit( QChar c ) +{ + return c.isDigit(); +} + +bool Q3TextStream::ts_isspace( QChar c ) +{ + return c.isSpace(); +} + +void Q3TextStream::ts_ungetc( QChar c ) +{ + if ( c.unicode() == 0xffff ) + return; + + d->ungetcBuf.prepend( c ); +} + +/*! + \since 4.2 + + Reads \a len bytes from the stream into \a s and returns a + reference to the stream. + + The buffer \a s must be preallocated. + + Note that no encoding is done by this function. + + \warning The behavior of this function is undefined unless the + stream's encoding is set to Unicode or Latin1. + + \sa QIODevice::readBlock() +*/ + +Q3TextStream &Q3TextStream::readRawBytes( char *s, uint len ) +{ + dev->readBlock( s, len ); + return *this; +} + +/*! + \since 4.2 + + Writes the \a len bytes from \a s to the stream and returns a + reference to the stream. + + Note that no encoding is done by this function. + + \sa QIODevice::writeBlock() +*/ + +Q3TextStream &Q3TextStream::writeRawBytes( const char* s, uint len ) +{ + dev->writeBlock( s, len ); + return *this; +} + + +Q3TextStream &Q3TextStream::writeBlock( const char* p, uint len ) +{ + if ( doUnicodeHeader ) { + doUnicodeHeader = FALSE; + if ( !mapper && !latin1 ) { + ts_putc( QChar::ByteOrderMark ); + } + } + // QCString and const char * are treated as Latin-1 + if ( !mapper && latin1 ) { + dev->writeBlock( p, len ); + } else if ( !mapper && internalOrder ) { + QChar *u = new QChar[len]; + for ( uint i = 0; i < len; i++ ) + u[i] = QLatin1Char(p[i]); + dev->writeBlock( (char*)u, len * sizeof(QChar) ); + delete [] u; + } +#ifndef QT_NO_TEXTCODEC + else if (mapper) { + QString s = QString::fromLatin1(p, len); + int l = len; + Q3CString block = mapper->fromUnicode(s.data(), l );//, &mapperReadState ); + dev->writeBlock( block ); + } +#endif + else { + for ( uint i = 0; i < len; i++ ) + ts_putc( (uchar)p[i] ); + } + return *this; +} + +Q3TextStream &Q3TextStream::writeBlock( const QChar* p, uint len ) +{ +#ifndef QT_NO_TEXTCODEC + if ( mapper ) { + QConstString s( p, len ); + int l = len; + Q3CString block = mapper->fromUnicode( s.string().data(), l );//, &mapperReadState ); + dev->writeBlock( block ); + } else +#endif + if ( latin1 ) { + dev->write(QString( p, len ).toLatin1()); + } else if ( internalOrder ) { + if ( doUnicodeHeader ) { + doUnicodeHeader = FALSE; + ts_putc( QChar::ByteOrderMark ); + } + dev->writeBlock( (char*)p, sizeof(QChar)*len ); + } else { + for (uint i=0; i<len; i++) + ts_putc( p[i] ); + } + return *this; +} + +/*! + \since 4.2 + + Resets the text stream. + + \list + \i All flags are set to 0. + \i The field width is set to 0. + \i The fill character is set to ' ' (Space). + \i The precision is set to 6. + \endlist + + \sa setf(), width(), fill(), precision() +*/ + +void Q3TextStream::reset() +{ + fflags = 0; + fwidth = 0; + fillchar = ' '; + fprec = 6; +} + +/*! + \fn QIODevice *Q3TextStream::device() const + \since 4.2 + + Returns the IO device currently set. + + \sa setDevice(), unsetDevice() +*/ + +/*! + \since 4.2 + + Sets the IO device to \a iod. + + \sa device(), unsetDevice() +*/ + +void Q3TextStream::setDevice( QIODevice *iod ) +{ + if ( owndev ) { + delete dev; + owndev = FALSE; + } + dev = iod; + d->sourceType = Q3TextStreamPrivate::IODevice; +} + +/*! + \since 4.2 + + Unsets the IO device. Equivalent to setDevice( 0 ). + + \sa device(), setDevice() +*/ + +void Q3TextStream::unsetDevice() +{ + setDevice( 0 ); + d->sourceType = Q3TextStreamPrivate::NotSet; +} + +/*! + \fn bool Q3TextStream::atEnd() const + \since 4.2 + + Returns TRUE if the IO device has reached the end position (end of + the stream or file) or if there is no IO device set; otherwise + returns FALSE. + + \sa QIODevice::atEnd() +*/ + +/*!\fn bool Q3TextStream::eof() const + + \obsolete + + This function has been renamed to atEnd(). + + \sa QIODevice::atEnd() +*/ + +/***************************************************************************** + Q3TextStream read functions + *****************************************************************************/ + + +/*! + \overload + + Reads a char \a c from the stream and returns a reference to the + stream. Note that whitespace is skipped. +*/ + +Q3TextStream &Q3TextStream::operator>>( char &c ) +{ + CHECK_STREAM_PRECOND + c = eat_ws().toLatin1(); + return *this; +} + +/*! + Reads a char \a c from the stream and returns a reference to the + stream. Note that whitespace is \e not skipped. +*/ + +Q3TextStream &Q3TextStream::operator>>( QChar &c ) +{ + CHECK_STREAM_PRECOND + c = ts_getc(); + return *this; +} + + +ulong Q3TextStream::input_bin() +{ + ulong val = 0; + QChar ch = eat_ws(); + int dv = ch.digitValue(); + while ( dv == 0 || dv == 1 ) { + val = ( val << 1 ) + dv; + ch = ts_getc(); + dv = ch.digitValue(); + } + if ( ch != QEOF ) + ts_ungetc( ch ); + return val; +} + +ulong Q3TextStream::input_oct() +{ + ulong val = 0; + QChar ch = eat_ws(); + int dv = ch.digitValue(); + while ( dv >= 0 && dv <= 7 ) { + val = ( val << 3 ) + dv; + ch = ts_getc(); + dv = ch.digitValue(); + } + if ( dv == 8 || dv == 9 ) { + while ( ts_isdigit(ch) ) + ch = ts_getc(); + } + if ( ch != QEOF ) + ts_ungetc( ch ); + return val; +} + +ulong Q3TextStream::input_dec() +{ + ulong val = 0; + QChar ch = eat_ws(); + int dv = ch.digitValue(); + while ( ts_isdigit(ch) ) { + val = val * 10 + dv; + ch = ts_getc(); + dv = ch.digitValue(); + } + if ( ch != QEOF ) + ts_ungetc( ch ); + return val; +} + +ulong Q3TextStream::input_hex() +{ + ulong val = 0; + QChar ch = eat_ws(); + char c = ch.toLatin1(); + while ( isxdigit((uchar) c) ) { + val <<= 4; + if ( ts_isdigit(QLatin1Char(c)) ) + val += c - '0'; + else + val += 10 + tolower( (uchar) c ) - 'a'; + ch = ts_getc(); + c = ch.toLatin1(); + } + if ( ch != QEOF ) + ts_ungetc( ch ); + return val; +} + +long Q3TextStream::input_int() +{ + long val; + QChar ch; + char c; + switch ( flags() & basefield ) { + case bin: + val = (long)input_bin(); + break; + case oct: + val = (long)input_oct(); + break; + case dec: + ch = eat_ws(); + c = ch.toLatin1(); + if ( ch == QEOF ) { + val = 0; + } else { + if ( !(c == '-' || c == '+') ) + ts_ungetc( ch ); + if ( c == '-' ) { + ulong v = input_dec(); + if ( v ) { // ensure that LONG_MIN can be read + v--; + val = -((long)v) - 1; + } else { + val = 0; + } + } else { + val = (long)input_dec(); + } + } + break; + case hex: + val = (long)input_hex(); + break; + default: + val = 0; + ch = eat_ws(); + c = ch.toLatin1(); + if ( c == '0' ) { // bin, oct or hex + ch = ts_getc(); + c = ch.toLatin1(); + if ( tolower((uchar) c) == 'x' ) + val = (long)input_hex(); + else if ( tolower((uchar) c) == 'b' ) + val = (long)input_bin(); + else { // octal + ts_ungetc( ch ); + if ( c >= '0' && c <= '7' ) { + val = (long)input_oct(); + } else { + val = 0; + } + } + } else if ( ts_isdigit(ch) ) { + ts_ungetc( ch ); + val = (long)input_dec(); + } else if ( c == '-' || c == '+' ) { + ulong v = input_dec(); + if ( c == '-' ) { + if ( v ) { // ensure that LONG_MIN can be read + v--; + val = -((long)v) - 1; + } else { + val = 0; + } + } else { + val = (long)v; + } + } + } + return val; +} + +// +// We use a table-driven FSM to parse floating point numbers +// strtod() cannot be used directly since we're reading from a QIODevice +// + +double Q3TextStream::input_double() +{ + const int Init = 0; // states + const int Sign = 1; + const int Mantissa = 2; + const int Dot = 3; + const int Abscissa = 4; + const int ExpMark = 5; + const int ExpSign = 6; + const int Exponent = 7; + const int Done = 8; + + const int InputSign = 1; // input tokens + const int InputDigit = 2; + const int InputDot = 3; + const int InputExp = 4; + + static const uchar table[8][5] = { + /* None InputSign InputDigit InputDot InputExp */ + { 0, Sign, Mantissa, Dot, 0, }, // Init + { 0, 0, Mantissa, Dot, 0, }, // Sign + { Done, Done, Mantissa, Dot, ExpMark,}, // Mantissa + { 0, 0, Abscissa, 0, 0, }, // Dot + { Done, Done, Abscissa, Done, ExpMark,}, // Abscissa + { 0, ExpSign, Exponent, 0, 0, }, // ExpMark + { 0, 0, Exponent, 0, 0, }, // ExpSign + { Done, Done, Exponent, Done, Done } // Exponent + }; + + int state = Init; // parse state + int input; // input token + + char buf[256]; + int i = 0; + QChar c = eat_ws(); + + for (;;) { + + switch ( c.toLatin1() ) { + case '+': + case '-': + input = InputSign; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + input = InputDigit; + break; + case '.': + input = InputDot; + break; + case 'e': + case 'E': + input = InputExp; + break; + default: + input = 0; + break; + } + + state = table[state][input]; + + if ( state == 0 || state == Done || i > 250 ) { + if ( i > 250 ) { // ignore rest of digits + do { c = ts_getc(); } while ( c != QEOF && ts_isdigit(c) ); + } + if ( c != QEOF ) + ts_ungetc( c ); + buf[i] = '\0'; + char *end; + return strtod( buf, &end ); + } + + buf[i++] = c.toLatin1(); + c = ts_getc(); + } + +#if !defined(Q_CC_EDG) + return 0.0; +#endif +} + + +/*! + \overload + + Reads a signed \c short integer \a i from the stream and returns a + reference to the stream. See flags() for an explanation of the + expected input format. +*/ + +Q3TextStream &Q3TextStream::operator>>( signed short &i ) +{ + CHECK_STREAM_PRECOND + i = (signed short)input_int(); + return *this; +} + + +/*! + \overload + + Reads an unsigned \c short integer \a i from the stream and + returns a reference to the stream. See flags() for an explanation + of the expected input format. +*/ + +Q3TextStream &Q3TextStream::operator>>( unsigned short &i ) +{ + CHECK_STREAM_PRECOND + i = (unsigned short)input_int(); + return *this; +} + + +/*! + \overload + + Reads a signed \c int \a i from the stream and returns a reference + to the stream. See flags() for an explanation of the expected + input format. +*/ + +Q3TextStream &Q3TextStream::operator>>( signed int &i ) +{ + CHECK_STREAM_PRECOND + i = (signed int)input_int(); + return *this; +} + + +/*! + \overload + + Reads an unsigned \c int \a i from the stream and returns a + reference to the stream. See flags() for an explanation of the + expected input format. +*/ + +Q3TextStream &Q3TextStream::operator>>( unsigned int &i ) +{ + CHECK_STREAM_PRECOND + i = (unsigned int)input_int(); + return *this; +} + + +/*! + \overload + + Reads a signed \c long int \a i from the stream and returns a + reference to the stream. See flags() for an explanation of the + expected input format. +*/ + +Q3TextStream &Q3TextStream::operator>>( signed long &i ) +{ + CHECK_STREAM_PRECOND + i = (signed long)input_int(); + return *this; +} + + +/*! + \overload + + Reads an unsigned \c long int \a i from the stream and returns a + reference to the stream. See flags() for an explanation of the + expected input format. +*/ + +Q3TextStream &Q3TextStream::operator>>( unsigned long &i ) +{ + CHECK_STREAM_PRECOND + i = (unsigned long)input_int(); + return *this; +} + + +/*! + \overload + + Reads a \c float \a f from the stream and returns a reference to + the stream. See flags() for an explanation of the expected input + format. +*/ + +Q3TextStream &Q3TextStream::operator>>( float &f ) +{ + CHECK_STREAM_PRECOND + f = (float)input_double(); + return *this; +} + + +/*! + \overload + + Reads a \c double \a f from the stream and returns a reference to + the stream. See flags() for an explanation of the expected input + format. +*/ + +Q3TextStream &Q3TextStream::operator>>( double &f ) +{ + CHECK_STREAM_PRECOND + f = input_double(); + return *this; +} + + +/*! + \overload + + Reads a "word" from the stream into \a s and returns a reference + to the stream. + + A word consists of characters for which isspace() returns FALSE. +*/ + +Q3TextStream &Q3TextStream::operator>>( char *s ) +{ + CHECK_STREAM_PRECOND + int maxlen = width( 0 ); + QChar c = eat_ws(); + if ( !maxlen ) + maxlen = -1; + while ( c != QEOF ) { + if ( ts_isspace(c) || maxlen-- == 0 ) { + ts_ungetc( c ); + break; + } + *s++ = c.toLatin1(); + c = ts_getc(); + } + + *s = '\0'; + return *this; +} + +/*! + \overload + + Reads a "word" from the stream into \a str and returns a reference + to the stream. + + A word consists of characters for which isspace() returns FALSE. +*/ + +Q3TextStream &Q3TextStream::operator>>( QString &str ) +{ + CHECK_STREAM_PRECOND + str=QString::fromLatin1(""); + QChar c = eat_ws(); + + while ( c != QEOF ) { + if ( ts_isspace(c) ) { + ts_ungetc( c ); + break; + } + + str += c; + c = ts_getc(); + } + + return *this; +} + +/*! + \overload + + Reads a "word" from the stream into \a str and returns a reference + to the stream. + + A word consists of characters for which isspace() returns FALSE. +*/ + +Q3TextStream &Q3TextStream::operator>>( Q3CString &str ) +{ + CHECK_STREAM_PRECOND + Q3CString *dynbuf = 0; + const int buflen = 256; + char buffer[buflen]; + char *s = buffer; + int i = 0; + QChar c = eat_ws(); + + while ( c != QEOF ) { + if ( ts_isspace(c) ) { + ts_ungetc( c ); + break; + } + if ( i >= buflen-1 ) { + if ( !dynbuf ) { // create dynamic buffer + dynbuf = new Q3CString(buflen*2); + memcpy( dynbuf->data(), s, i ); // copy old data + } else if ( i >= (int)dynbuf->size()-1 ) { + dynbuf->resize( dynbuf->size()*2 ); + } + s = dynbuf->data(); + } + s[i++] = c.toLatin1(); + c = ts_getc(); + } + str.resize( i ); + memcpy( str.data(), s, i ); + + delete dynbuf; + return *this; +} + + +/*! + \since 4.2 + + Reads a line from the stream and returns a string containing the + text. + + The returned string does not contain any trailing newline or + carriage return. Note that this is different from + QIODevice::readLine(), which does not strip the newline at the end + of the line. + + On EOF you will get a QString that is null. On reading an empty + line the returned QString is empty but not null. + + \sa QIODevice::readLine() +*/ + +QString Q3TextStream::readLine() +{ +#if defined(QT_CHECK_STATE) + if ( !dev ) { + qWarning( "Q3TextStream::readLine: No device" ); + return QString::null; + } +#endif + bool readCharByChar = TRUE; + QString result; +#if 0 + if ( !doUnicodeHeader && ( + (latin1) || + (mapper != 0 && mapper->mibEnum() == 106 ) // UTF 8 + ) ) { + readCharByChar = FALSE; + // use optimized read line + QChar c[getline_buf_size]; + int pos = 0; + bool eof = FALSE; + + for (;;) { + pos = ts_getline( c ); + if ( pos == 0 ) { + // something went wrong; try fallback + readCharByChar = TRUE; + //dev->resetStatus(); + break; + } + if ( c[pos-1] == QEOF || c[pos-1] == '\n' ) { + if ( pos>2 && c[pos-1]==QEOF && c[pos-2]=='\n' ) { + result += QString( c, pos-2 ); + } else if ( pos > 1 ) { + result += QString( c, pos-1 ); + } + if ( pos == 1 && c[pos-1] == QEOF ) + eof = TRUE; + break; + } else { + result += QString( c, pos ); + } + } + if ( eof && result.isEmpty() ) + return QString::null; + } +#endif + if ( readCharByChar ) { + const int buf_size = 256; + QChar c[buf_size]; + int pos = 0; + + c[pos] = ts_getc(); + if ( c[pos] == QEOF ) + return QString::null; + + while ( c[pos] != QEOF && c[pos] != QLatin1Char('\n') ) { + if ( c[pos] == QLatin1Char('\r') ) { // ( handle mac and dos ) + QChar nextc = ts_getc(); + if ( nextc != QLatin1Char('\n') ) + ts_ungetc( nextc ); + break; + } + pos++; + if ( pos >= buf_size ) { + result += QString( c, pos ); + pos = 0; + } + c[pos] = ts_getc(); + } + result += QString( c, pos ); + } + + return result; +} + + +/*! + \since 4.2 + + Reads the entire stream from the current position, and returns a string + containing the text. + + \sa readLine() +*/ + +QString Q3TextStream::read() +{ +#if defined(QT_CHECK_STATE) + if ( !dev ) { + qWarning( "Q3TextStream::read: No device" ); + return QString::null; + } +#endif + QString result; + const uint bufsize = 512; + QChar buf[bufsize]; + uint i, num, start; + bool skipped_cr = FALSE; + + for (;;) { + num = ts_getbuf(buf,bufsize); + // convert dos (\r\n) and mac (\r) style eol to unix style (\n) + start = 0; + for ( i=0; i<num; i++ ) { + if ( buf[i] == QLatin1Char('\r') ) { + // Only skip single cr's preceding lf's + if ( skipped_cr ) { + result += buf[i]; + start++; + } else { + result += QString( &buf[start], i-start ); + start = i+1; + skipped_cr = TRUE; + } + } else { + if ( skipped_cr ) { + if ( buf[i] != QLatin1Char('\n') ) { + // Should not have skipped it + result += QLatin1Char('\n'); + } + skipped_cr = FALSE; + } + } + } + if ( start < num ) + result += QString( &buf[start], i-start ); + if ( num != bufsize ) // if ( EOF ) + break; + } + return result; +} + + + +/***************************************************************************** + Q3TextStream write functions + *****************************************************************************/ + +/*! + \since 4.2 + + Writes character \c char to the stream and returns a reference to + the stream. + + The character \a c is assumed to be Latin1 encoded independent of + the Encoding set for the Q3TextStream. +*/ +Q3TextStream &Q3TextStream::operator<<( QChar c ) +{ + CHECK_STREAM_PRECOND + ts_putc( c ); + return *this; +} + +/*! + \overload + \since 4.2 + + Writes character \a c to the stream and returns a reference to the + stream. +*/ +Q3TextStream &Q3TextStream::operator<<( char c ) +{ + CHECK_STREAM_PRECOND + unsigned char uc = (unsigned char) c; + ts_putc( uc ); + return *this; +} + +Q3TextStream &Q3TextStream::output_int( int format, ulong n, bool neg ) +{ + static const char hexdigits_lower[] = "0123456789abcdef"; + static const char hexdigits_upper[] = "0123456789ABCDEF"; + CHECK_STREAM_PRECOND + char buf[76]; + register char *p; + int len; + const char *hexdigits; + + switch ( flags() & I_BASE_MASK ) { + + case I_BASE_2: // output binary number + switch ( format & I_TYPE_MASK ) { + case I_SHORT: len=16; break; + case I_INT: len=sizeof(int)*8; break; + case I_LONG: len=32; break; + default: len = 0; + } + p = &buf[74]; // go reverse order + *p = '\0'; + while ( len-- ) { + *--p = (char)(n&1) + '0'; + n >>= 1; + if ( !n ) + break; + } + if ( flags() & showbase ) { // show base + *--p = (flags() & uppercase) ? 'B' : 'b'; + *--p = '0'; + } + break; + + case I_BASE_8: // output octal number + p = &buf[74]; + *p = '\0'; + do { + *--p = (char)(n&7) + '0'; + n >>= 3; + } while ( n ); + if ( flags() & showbase ) + *--p = '0'; + break; + + case I_BASE_16: // output hexadecimal number + p = &buf[74]; + *p = '\0'; + hexdigits = (flags() & uppercase) ? + hexdigits_upper : hexdigits_lower; + do { + *--p = hexdigits[(int)n&0xf]; + n >>= 4; + } while ( n ); + if ( flags() & showbase ) { + *--p = (flags() & uppercase) ? 'X' : 'x'; + *--p = '0'; + } + break; + + default: // decimal base is default + p = &buf[74]; + *p = '\0'; + if ( neg ) + n = (ulong)(-(long)n); + do { + *--p = ((int)(n%10)) + '0'; + n /= 10; + } while ( n ); + if ( neg ) + *--p = '-'; + else if ( flags() & showpos ) + *--p = '+'; + if ( (flags() & internal) && fwidth && !ts_isdigit(QLatin1Char(*p)) ) { + ts_putc( *p ); // special case for internal + ++p; // padding + fwidth--; + return *this << (const char*)p; + } + } + if ( fwidth ) { // adjustment required + if ( !(flags() & left) ) { // but NOT left adjustment + len = qstrlen(p); + int padlen = fwidth - len; + if ( padlen <= 0 ) { // no padding required + writeBlock( p, len ); + } else if ( padlen < (int)(p-buf) ) { // speeds up padding + memset( p-padlen, (char)fillchar, padlen ); + writeBlock( p-padlen, padlen+len ); + } + else // standard padding + *this << (const char*)p; + } + else + *this << (const char*)p; + fwidth = 0; // reset field width + } + else { + writeBlock( p, qstrlen(p) ); + } + return *this; +} + + +/*! + \overload + \since 4.2 + + Writes a \c short integer \a i to the stream and returns a + reference to the stream. +*/ + +Q3TextStream &Q3TextStream::operator<<( signed short i ) +{ + return output_int( I_SHORT | I_SIGNED, i, i < 0 ); +} + + +/*! + \overload + \since 4.2 + + Writes an \c unsigned \c short integer \a i to the stream and + returns a reference to the stream. +*/ + +Q3TextStream &Q3TextStream::operator<<( unsigned short i ) +{ + return output_int( I_SHORT | I_UNSIGNED, i, FALSE ); +} + + +/*! + \overload + \since 4.2 + + Writes an \c int \a i to the stream and returns a reference to the + stream. +*/ + +Q3TextStream &Q3TextStream::operator<<( signed int i ) +{ + return output_int( I_INT | I_SIGNED, i, i < 0 ); +} + + +/*! + \overload + \since 4.2 + + Writes an \c unsigned \c int \a i to the stream and returns a + reference to the stream. +*/ + +Q3TextStream &Q3TextStream::operator<<( unsigned int i ) +{ + return output_int( I_INT | I_UNSIGNED, i, FALSE ); +} + + +/*! + \overload + \since 4.2 + + Writes a \c long \c int \a i to the stream and returns a reference + to the stream. +*/ + +Q3TextStream &Q3TextStream::operator<<( signed long i ) +{ + return output_int( I_LONG | I_SIGNED, i, i < 0 ); +} + + +/*! + \overload + \since 4.2 + + Writes an \c unsigned \c long \c int \a i to the stream and + returns a reference to the stream. +*/ + +Q3TextStream &Q3TextStream::operator<<( unsigned long i ) +{ + return output_int( I_LONG | I_UNSIGNED, i, FALSE ); +} + + +/*! + \overload + \since 4.2 + + Writes a \c float \a f to the stream and returns a reference to + the stream. +*/ + +Q3TextStream &Q3TextStream::operator<<( float f ) +{ + return *this << (double)f; +} + +/*! + \overload + \since 4.2 + + Writes a \c double \a f to the stream and returns a reference to + the stream. +*/ + +Q3TextStream &Q3TextStream::operator<<( double f ) +{ + CHECK_STREAM_PRECOND + char f_char; + char format[16]; + if ( (flags()&floatfield) == fixed ) + f_char = 'f'; + else if ( (flags()&floatfield) == scientific ) + f_char = (flags() & uppercase) ? 'E' : 'e'; + else + f_char = (flags() & uppercase) ? 'G' : 'g'; + register char *fs = format; // generate format string + *fs++ = '%'; // "%.<prec>l<f_char>" + *fs++ = '.'; + int prec = precision(); + if ( prec > 99 ) + prec = 99; + if ( prec >= 10 ) { + *fs++ = prec / 10 + '0'; + *fs++ = prec % 10 + '0'; + } else { + *fs++ = prec + '0'; + } + *fs++ = 'l'; + *fs++ = f_char; + *fs = '\0'; + QString num; + num.sprintf(format, f); // convert to text + if ( fwidth ) // padding + *this << num.latin1(); + else // just write it + writeBlock(num.latin1(), num.length()); + return *this; +} + + +/*! + \overload + \since 4.2 + + Writes a string to the stream and returns a reference to the + stream. + + The string \a s is assumed to be Latin1 encoded independent of the + Encoding set for the Q3TextStream. +*/ + +Q3TextStream &Q3TextStream::operator<<( const char* s ) +{ + CHECK_STREAM_PRECOND + char padbuf[48]; + uint len = qstrlen( s ); // don't write null terminator + if ( fwidth ) { // field width set + int padlen = fwidth - len; + fwidth = 0; // reset width + if ( padlen > 0 ) { + char *ppad; + if ( padlen > 46 ) { // create extra big fill buffer + ppad = new char[padlen]; + Q_CHECK_PTR( ppad ); + } else { + ppad = padbuf; + } + memset( ppad, (char)fillchar, padlen ); // fill with fillchar + if ( !(flags() & left) ) { + writeBlock( ppad, padlen ); + padlen = 0; + } + writeBlock( s, len ); + if ( padlen ) + writeBlock( ppad, padlen ); + if ( ppad != padbuf ) // delete extra big fill buf + delete[] ppad; + return *this; + } + } + writeBlock( s, len ); + return *this; +} + +/*! + \overload + \since 4.2 + + Writes \a s to the stream and returns a reference to the stream. + + The string \a s is assumed to be Latin1 encoded independent of the + Encoding set for the Q3TextStream. +*/ + +Q3TextStream &Q3TextStream::operator<<( const Q3CString & s ) +{ + return operator<<(s.data()); +} + +/*! + \overload + \since 4.2 + + Writes \a s to the stream and returns a reference to the stream. +*/ + +Q3TextStream &Q3TextStream::operator<<( const QString& s ) +{ + if ( !mapper && latin1 ) + return operator<<(s.latin1()); + CHECK_STREAM_PRECOND + QString s1 = s; + if ( fwidth ) { // field width set + if ( !(flags() & left) ) { + s1 = s.rightJustify(fwidth, QLatin1Char((char)fillchar)); + } else { + s1 = s.leftJustify(fwidth, QLatin1Char((char)fillchar)); + } + fwidth = 0; // reset width + } + writeBlock( s1.unicode(), s1.length() ); + return *this; +} + + +/*! + \overload + \since 4.2 + + Writes a pointer to the stream and returns a reference to the + stream. + + The \a ptr is output as an unsigned long hexadecimal integer. +*/ + +Q3TextStream &Q3TextStream::operator<<( void *ptr ) +{ + int f = flags(); + setf( hex, basefield ); + setf( showbase ); + unsetf( uppercase ); + output_int( I_LONG | I_UNSIGNED, (ulong)ptr, FALSE ); + flags( f ); + return *this; +} + + +/*! + \fn int Q3TextStream::flags() const + \since 4.2 + + Returns the current stream flags. The default value is 0. + + \table + \header \i Flag \i Meaning + \row \i \c skipws \i Not currently used; whitespace always skipped + \row \i \c left \i Numeric fields are left-aligned + \row \i \c right + \i Not currently used (by default, numerics are right-aligned) + \row \i \c internal \i Puts any padding spaces between +/- and value + \row \i \c bin \i Output \e and input only in binary + \row \i \c oct \i Output \e and input only in octal + \row \i \c dec \i Output \e and input only in decimal + \row \i \c hex \i Output \e and input only in hexadecimal + \row \i \c showbase + \i Annotates numeric outputs with 0b, 0, or 0x if in \c bin, + \c oct, or \c hex format + \row \i \c showpoint \i Not currently used + \row \i \c uppercase \i Uses 0B and 0X rather than 0b and 0x + \row \i \c showpos \i Shows + for positive numeric values + \row \i \c scientific \i Uses scientific notation for floating point values + \row \i \c fixed \i Uses fixed-point notation for floating point values + \endtable + + Note that unless \c bin, \c oct, \c dec, or \c hex is set, the + input base is octal if the value starts with 0, hexadecimal if it + starts with 0x, binary if it starts with 0b, and decimal + otherwise. + + \sa setf(), unsetf() +*/ + +/*! + \fn int Q3TextStream::flags( int f ) + + \overload + + Sets the stream flags to \a f. Returns the previous stream flags. + + \sa setf(), unsetf(), flags() +*/ + +/*! + \fn int Q3TextStream::setf( int bits ) + \since 4.2 + + Sets the stream flag bits \a bits. Returns the previous stream + flags. + + Equivalent to \c{flags( flags() | bits )}. + + \sa unsetf() +*/ + +/*! + \fn int Q3TextStream::setf( int bits, int mask ) + + \overload + + Sets the stream flag bits \a bits with a bit mask \a mask. Returns + the previous stream flags. + + Equivalent to \c{flags( (flags() & ~mask) | (bits & mask) )}. + + \sa setf(), unsetf() +*/ + +/*! + \fn int Q3TextStream::unsetf( int bits ) + \since 4.2 + + Clears the stream flag bits \a bits. Returns the previous stream + flags. + + Equivalent to \c{flags( flags() & ~mask )}. + + \sa setf() +*/ + +/*! + \fn int Q3TextStream::width() const + \since 4.2 + + Returns the field width. The default value is 0. +*/ + +/*! + \fn int Q3TextStream::width( int w ) + + \overload + + Sets the field width to \a w. Returns the previous field width. +*/ + +/*! + \fn int Q3TextStream::fill() const + \since 4.2 + + Returns the fill character. The default value is ' ' (space). +*/ + +/*! + \fn int Q3TextStream::fill( int f ) + \overload + + Sets the fill character to \a f. Returns the previous fill character. +*/ + +/*! + \fn int Q3TextStream::precision() const + \since 4.2 + + Returns the precision. The default value is 6. +*/ + +/*! + \fn int Q3TextStream::precision( int p ) + + \overload + + Sets the precision to \a p. Returns the previous precision setting. +*/ + + +Q3TextStream &bin( Q3TextStream &s ) +{ + s.setf(Q3TextStream::bin,Q3TextStream::basefield); + return s; +} + +Q3TextStream &oct( Q3TextStream &s ) +{ + s.setf(Q3TextStream::oct,Q3TextStream::basefield); + return s; +} + +Q3TextStream &dec( Q3TextStream &s ) +{ + s.setf(Q3TextStream::dec,Q3TextStream::basefield); + return s; +} + +Q3TextStream &hex( Q3TextStream &s ) +{ + s.setf(Q3TextStream::hex,Q3TextStream::basefield); + return s; +} + +Q3TextStream &endl( Q3TextStream &s ) +{ + return s << '\n'; +} + +Q3TextStream &flush( Q3TextStream &s ) +{ + return s; +} + +Q3TextStream &ws( Q3TextStream &s ) +{ + s.skipWhiteSpace(); + return s; +} + +Q3TextStream &reset( Q3TextStream &s ) +{ + s.reset(); + return s; +} + +/*! + \since 4.2 + + Sets the encoding of this stream to \a e, where \a e is one of the + following values: + \table + \header \i Encoding \i Meaning + \row \i Locale + \i Uses local file format (Latin1 if locale is not set), but + autodetecting Unicode(utf16) on input. + \row \i Unicode + \i Uses Unicode(utf16) for input and output. Output will be + written in the order most efficient for the current platform + (i.e. the order used internally in QString). + \row \i UnicodeUTF8 + \i Using Unicode(utf8) for input and output. If you use it for + input it will autodetect utf16 and use it instead of utf8. + \row \i Latin1 + \i ISO-8859-1. Will not autodetect utf16. + \row \i UnicodeNetworkOrder + \i Uses network order Unicode(utf16) for input and output. + Useful when reading Unicode data that does not start with the + byte order marker. + \row \i UnicodeReverse + \i Uses reverse network order Unicode(utf16) for input and + output. Useful when reading Unicode data that does not start + with the byte order marker or when writing data that should be + read by buggy Windows applications. + \row \i RawUnicode + \i Like Unicode, but does not write the byte order marker nor + does it auto-detect the byte order. Useful only when writing to + non-persistent storage used by a single process. + \endtable + + \c Locale and all Unicode encodings, except \c RawUnicode, will look + at the first two bytes in an input stream to determine the byte + order. The initial byte order marker will be stripped off before + data is read. + + Note that this function should be called before any data is read to + or written from the stream. + + \sa setCodec() +*/ + +void Q3TextStream::setEncoding( Encoding e ) +{ + resetCodecConverterState(&mapperReadState); + resetCodecConverterState(&mapperWriteState); + + if ( d->sourceType == Q3TextStreamPrivate::String ) + return; + + switch ( e ) { + case Unicode: + mapper = 0; + latin1 = FALSE; + doUnicodeHeader = TRUE; + internalOrder = TRUE; + networkOrder = QChar::networkOrdered(); + break; + case UnicodeUTF8: +#ifndef QT_NO_TEXTCODEC + mapper = QTextCodec::codecForMib( 106 ); + mapperWriteState.flags |= QTextCodec::IgnoreHeader; + latin1 = FALSE; + doUnicodeHeader = TRUE; + internalOrder = TRUE; + networkOrder = QChar::networkOrdered(); +#else + mapper = 0; + latin1 = TRUE; + doUnicodeHeader = TRUE; +#endif + break; + case UnicodeNetworkOrder: + mapper = 0; + latin1 = FALSE; + doUnicodeHeader = TRUE; + internalOrder = QChar::networkOrdered(); + networkOrder = TRUE; + break; + case UnicodeReverse: + mapper = 0; + latin1 = FALSE; + doUnicodeHeader = TRUE; + internalOrder = !QChar::networkOrdered(); + networkOrder = FALSE; + break; + case RawUnicode: + mapper = 0; + latin1 = FALSE; + doUnicodeHeader = FALSE; + internalOrder = TRUE; + networkOrder = QChar::networkOrdered(); + break; + case Locale: + latin1 = TRUE; // fallback to Latin-1 +#ifndef QT_NO_TEXTCODEC + mapper = QTextCodec::codecForLocale(); + mapperReadState.flags |= QTextCodec::IgnoreHeader; + mapperWriteState.flags |= QTextCodec::IgnoreHeader; + // optimized Latin-1 processing +#if defined(Q_OS_WIN32) + if ( GetACP() == 1252 ) + mapper = 0; +#endif + if ( mapper && mapper->mibEnum() == 4 ) +#endif + mapper = 0; + + doUnicodeHeader = TRUE; // If it reads as Unicode, accept it + break; + case Latin1: + mapper = 0; + doUnicodeHeader = FALSE; + latin1 = TRUE; + break; + } +} + + +#ifndef QT_NO_TEXTCODEC +/*! + \since 4.2 + + Sets the codec for this stream to \a codec. Will not try to + autodetect Unicode. + + Note that this function should be called before any data is read + to/written from the stream. + + \sa setEncoding(), codec() +*/ + +void Q3TextStream::setCodec( QTextCodec *codec ) +{ + if ( d->sourceType == Q3TextStreamPrivate::String ) + return; // QString does not need any codec + mapper = codec; + latin1 = ( codec->mibEnum() == 4 ); + if ( latin1 ) + mapper = 0; + doUnicodeHeader = FALSE; +} + +/*! + Returns the codec actually used for this stream. + \since 4.2 + + If Unicode is automatically detected in input, a codec with \link + QTextCodec::name() name() \endlink "ISO-10646-UCS-2" is returned. + + \sa setCodec() +*/ + +QTextCodec *Q3TextStream::codec() +{ + if ( mapper ) { + return mapper; + } else { + // 4 is "ISO 8859-1", 1000 is "ISO-10646-UCS-2" + return QTextCodec::codecForMib( latin1 ? 4 : 1000 ); + } +} + +#endif + +QT_END_NAMESPACE + +#endif // QT_NO_TEXTSTREAM diff --git a/src/qt3support/text/q3textstream.h b/src/qt3support/text/q3textstream.h new file mode 100644 index 0000000..1c6e6d2 --- /dev/null +++ b/src/qt3support/text/q3textstream.h @@ -0,0 +1,297 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3TEXTSTREAM_H +#define Q3TEXTSTREAM_H + +#include <QtCore/qiodevice.h> +#include <QtCore/qstring.h> +#ifndef QT_NO_TEXTCODEC +#include <QtCore/qtextcodec.h> +#endif +#include <Qt3Support/q3cstring.h> + +#include <stdio.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3SupportLight) + +class Q3TextStreamPrivate; + +class Q_COMPAT_EXPORT Q3TextStream // text stream class +{ +public: + enum Encoding { Locale, Latin1, Unicode, UnicodeNetworkOrder, + UnicodeReverse, RawUnicode, UnicodeUTF8 }; + + void setEncoding( Encoding ); +#ifndef QT_NO_TEXTCODEC + void setCodec( QTextCodec* ); + QTextCodec *codec(); +#endif + + Q3TextStream(); + Q3TextStream( QIODevice * ); + Q3TextStream( QString*, int mode ); + Q3TextStream( QString&, int mode ); // obsolete + Q3TextStream( QByteArray&, int mode ); + Q3TextStream( FILE *, int mode ); + virtual ~Q3TextStream(); + + QIODevice *device() const; + void setDevice( QIODevice * ); + void unsetDevice(); + + bool atEnd() const; + bool eof() const; + + Q3TextStream &operator>>( QChar & ); + Q3TextStream &operator>>( char & ); + Q3TextStream &operator>>( signed short & ); + Q3TextStream &operator>>( unsigned short & ); + Q3TextStream &operator>>( signed int & ); + Q3TextStream &operator>>( unsigned int & ); + Q3TextStream &operator>>( signed long & ); + Q3TextStream &operator>>( unsigned long & ); + Q3TextStream &operator>>( float & ); + Q3TextStream &operator>>( double & ); + Q3TextStream &operator>>( char * ); + Q3TextStream &operator>>( QString & ); + Q3TextStream &operator>>( Q3CString & ); + + Q3TextStream &operator<<( QChar ); + Q3TextStream &operator<<( char ); + Q3TextStream &operator<<( signed short ); + Q3TextStream &operator<<( unsigned short ); + Q3TextStream &operator<<( signed int ); + Q3TextStream &operator<<( unsigned int ); + Q3TextStream &operator<<( signed long ); + Q3TextStream &operator<<( unsigned long ); + Q3TextStream &operator<<( float ); + Q3TextStream &operator<<( double ); + Q3TextStream &operator<<( const char* ); + Q3TextStream &operator<<( const QString & ); + Q3TextStream &operator<<( const Q3CString & ); + Q3TextStream &operator<<( void * ); // any pointer + + Q3TextStream &readRawBytes( char *, uint len ); + Q3TextStream &writeRawBytes( const char* , uint len ); + + QString readLine(); + QString read(); + void skipWhiteSpace(); + + enum { + skipws = 0x0001, // skip whitespace on input + left = 0x0002, // left-adjust output + right = 0x0004, // right-adjust output + internal = 0x0008, // pad after sign + bin = 0x0010, // binary format integer + oct = 0x0020, // octal format integer + dec = 0x0040, // decimal format integer + hex = 0x0080, // hex format integer + showbase = 0x0100, // show base indicator + showpoint = 0x0200, // force decimal point (float) + uppercase = 0x0400, // upper-case hex output + showpos = 0x0800, // add '+' to positive integers + scientific= 0x1000, // scientific float output + fixed = 0x2000 // fixed float output + }; + + static const int basefield; // bin | oct | dec | hex + static const int adjustfield; // left | right | internal + static const int floatfield; // scientific | fixed + + int flags() const; + int flags( int f ); + int setf( int bits ); + int setf( int bits, int mask ); + int unsetf( int bits ); + + void reset(); + + int width() const; + int width( int ); + int fill() const; + int fill( int ); + int precision() const; + int precision( int ); + +private: + long input_int(); + void init(); + Q3TextStream &output_int( int, ulong, bool ); + QIODevice *dev; + + int fflags; + int fwidth; + int fillchar; + int fprec; + bool doUnicodeHeader; + bool owndev; + QTextCodec *mapper; + QTextCodec::ConverterState mapperReadState; + QTextCodec::ConverterState mapperWriteState; + Q3TextStreamPrivate * d; + QChar unused1; // ### remove in Qt 4.0 + bool latin1; + bool internalOrder; + bool networkOrder; + void *unused2; // ### remove in Qt 4.0 + + QChar eat_ws(); + uint ts_getline( QChar* ); + void ts_ungetc( QChar ); + QChar ts_getc(); + uint ts_getbuf( QChar*, uint ); + void ts_putc(int); + void ts_putc(QChar); + bool ts_isspace(QChar); + bool ts_isdigit(QChar); + ulong input_bin(); + ulong input_oct(); + ulong input_dec(); + ulong input_hex(); + double input_double(); + Q3TextStream &writeBlock( const char* p, uint len ); + Q3TextStream &writeBlock( const QChar* p, uint len ); + +private: // Disabled copy constructor and operator= +#if defined(Q_DISABLE_COPY) + Q3TextStream( const Q3TextStream & ); + Q3TextStream &operator=( const Q3TextStream & ); +#endif +}; + +/***************************************************************************** + Q3TextStream inline functions + *****************************************************************************/ + +inline QIODevice *Q3TextStream::device() const +{ return dev; } + +inline bool Q3TextStream::atEnd() const +{ return dev ? dev->atEnd() : FALSE; } + +inline bool Q3TextStream::eof() const +{ return atEnd(); } + +inline int Q3TextStream::flags() const +{ return fflags; } + +inline int Q3TextStream::flags( int f ) +{ int oldf = fflags; fflags = f; return oldf; } + +inline int Q3TextStream::setf( int bits ) +{ int oldf = fflags; fflags |= bits; return oldf; } + +inline int Q3TextStream::setf( int bits, int mask ) +{ int oldf = fflags; fflags = (fflags & ~mask) | (bits & mask); return oldf; } + +inline int Q3TextStream::unsetf( int bits ) +{ int oldf = fflags; fflags &= ~bits; return oldf; } + +inline int Q3TextStream::width() const +{ return fwidth; } + +inline int Q3TextStream::width( int w ) +{ int oldw = fwidth; fwidth = w; return oldw; } + +inline int Q3TextStream::fill() const +{ return fillchar; } + +inline int Q3TextStream::fill( int f ) +{ int oldc = fillchar; fillchar = f; return oldc; } + +inline int Q3TextStream::precision() const +{ return fprec; } + +inline int Q3TextStream::precision( int p ) +{ int oldp = fprec; fprec = p; return oldp; } + +/*! + Returns one character from the stream, or EOF. +*/ +inline QChar Q3TextStream::ts_getc() +{ QChar r; return ( ts_getbuf( &r,1 ) == 1 ? r : QChar((ushort)0xffff) ); } + +/***************************************************************************** + Q3TextStream manipulators + *****************************************************************************/ + +typedef Q3TextStream & (*Q3TSFUNC)(Q3TextStream &);// manipulator function +typedef int (Q3TextStream::*Q3TSMFI)(int); // manipulator w/int argument + +class Q_COMPAT_EXPORT Q3TSManip { // text stream manipulator +public: + Q3TSManip( Q3TSMFI m, int a ) { mf=m; arg=a; } + void exec( Q3TextStream &s ) { (s.*mf)(arg); } +private: + Q3TSMFI mf; // Q3TextStream member function + int arg; // member function argument +}; + +Q_COMPAT_EXPORT inline Q3TextStream &operator>>( Q3TextStream &s, Q3TSFUNC f ) +{ return (*f)( s ); } + +Q_COMPAT_EXPORT inline Q3TextStream &operator<<( Q3TextStream &s, Q3TSFUNC f ) +{ return (*f)( s ); } + +Q_COMPAT_EXPORT inline Q3TextStream &operator<<( Q3TextStream &s, Q3TSManip m ) +{ m.exec(s); return s; } + +Q_COMPAT_EXPORT Q3TextStream &bin( Q3TextStream &s ); // set bin notation +Q_COMPAT_EXPORT Q3TextStream &oct( Q3TextStream &s ); // set oct notation +Q_COMPAT_EXPORT Q3TextStream &dec( Q3TextStream &s ); // set dec notation +Q_COMPAT_EXPORT Q3TextStream &hex( Q3TextStream &s ); // set hex notation +Q_COMPAT_EXPORT Q3TextStream &endl( Q3TextStream &s ); // insert EOL ('\n') +Q_COMPAT_EXPORT Q3TextStream &flush( Q3TextStream &s ); // flush output +Q_COMPAT_EXPORT Q3TextStream &ws( Q3TextStream &s ); // eat whitespace on input +Q_COMPAT_EXPORT Q3TextStream &reset( Q3TextStream &s ); // set default flags + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3TEXTSTREAM_H diff --git a/src/qt3support/text/q3textview.cpp b/src/qt3support/text/q3textview.cpp new file mode 100644 index 0000000..2625e8e --- /dev/null +++ b/src/qt3support/text/q3textview.cpp @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "q3textview.h" + +#ifndef QT_NO_TEXTVIEW + +QT_BEGIN_NAMESPACE + +/*! + \class Q3TextView + \brief The Q3TextView class provides a rich text viewer. + + \compat + + This class wraps a read-only \l Q3TextEdit. + Use a \l Q3TextEdit instead, and call setReadOnly(true) + to disable editing. +*/ + +/*! \internal */ + +Q3TextView::Q3TextView(const QString& text, const QString& context, + QWidget *parent, const char *name) + : Q3TextEdit(text, context, parent, name) +{ + setReadOnly(true); +} + +/*! \internal */ + +Q3TextView::Q3TextView(QWidget *parent, const char *name) + : Q3TextEdit(parent, name) +{ + setReadOnly(true); +} + +/*! \internal */ + +Q3TextView::~Q3TextView() +{ +} + +QT_END_NAMESPACE + +#endif diff --git a/src/qt3support/text/q3textview.h b/src/qt3support/text/q3textview.h new file mode 100644 index 0000000..2846b09 --- /dev/null +++ b/src/qt3support/text/q3textview.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Qt3Support module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef Q3TEXTVIEW_H +#define Q3TEXTVIEW_H + +#include <Qt3Support/q3textedit.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3SupportLight) + +#ifndef QT_NO_TEXTVIEW + +class Q_COMPAT_EXPORT Q3TextView : public Q3TextEdit +{ + Q_OBJECT + +public: + Q3TextView(const QString& text, const QString& context = QString(), + QWidget* parent=0, const char* name=0); + Q3TextView(QWidget* parent=0, const char* name=0); + + virtual ~Q3TextView(); + +private: + Q_DISABLE_COPY(Q3TextView) +}; + +#endif // QT_NO_TEXTVIEW + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // Q3TEXTVIEW_H diff --git a/src/qt3support/text/text.pri b/src/qt3support/text/text.pri new file mode 100644 index 0000000..0e74761 --- /dev/null +++ b/src/qt3support/text/text.pri @@ -0,0 +1,25 @@ +HEADERS += \ + text/q3syntaxhighlighter.h \ + text/q3syntaxhighlighter_p.h \ + text/q3textview.h \ + text/q3textbrowser.h \ + text/q3textedit.h \ + text/q3multilineedit.h \ + text/q3richtext_p.h \ + text/q3simplerichtext.h \ + text/q3stylesheet.h \ + text/q3textstream.h + +SOURCES += \ + text/q3syntaxhighlighter.cpp \ + text/q3textview.cpp \ + text/q3textbrowser.cpp \ + text/q3textedit.cpp \ + text/q3multilineedit.cpp \ + text/q3richtext.cpp \ + text/q3richtext_p.cpp \ + text/q3simplerichtext.cpp \ + text/q3stylesheet.cpp \ + text/q3textstream.cpp + +INCLUDEPATH += ../3rdparty/harfbuzz/src |