author | leseb <leseb> | 2002-07-14 21:21:35 (UTC) |
---|---|---|
committer | leseb <leseb> | 2002-07-14 21:21:35 (UTC) |
commit | 4feeec8b5b41cfd3d13274411f515524f687da09 (patch) (side-by-side diff) | |
tree | 002bbfb9997713e5d5975855d3cfbba7a71b9104 | |
parent | bdef9cf23ced569a9bc80c1d4f25d85861273b4a (diff) | |
download | opie-4feeec8b5b41cfd3d13274411f515524f687da09.zip opie-4feeec8b5b41cfd3d13274411f515524f687da09.tar.gz opie-4feeec8b5b41cfd3d13274411f515524f687da09.tar.bz2 |
opie-write first draft
-rw-r--r-- | noncore/apps/opie-write/main.cpp | 37 | ||||
-rw-r--r-- | noncore/apps/opie-write/mainwindow.cpp | 570 | ||||
-rw-r--r-- | noncore/apps/opie-write/mainwindow.h | 113 | ||||
-rw-r--r-- | noncore/apps/opie-write/opie-write.pro | 25 | ||||
-rw-r--r-- | noncore/apps/opie-write/qcleanuphandler.h | 139 | ||||
-rw-r--r-- | noncore/apps/opie-write/qcomplextext.cpp | 152 | ||||
-rw-r--r-- | noncore/apps/opie-write/qcomplextext_p.h | 133 | ||||
-rw-r--r-- | noncore/apps/opie-write/qrichtext.cpp | 8085 | ||||
-rw-r--r-- | noncore/apps/opie-write/qrichtext_p.cpp | 706 | ||||
-rw-r--r-- | noncore/apps/opie-write/qrichtext_p.h | 2158 | ||||
-rw-r--r-- | noncore/apps/opie-write/qstylesheet.cpp | 1484 | ||||
-rw-r--r-- | noncore/apps/opie-write/qstylesheet.h | 221 | ||||
-rw-r--r-- | noncore/apps/opie-write/qt3namespace.h | 28 | ||||
-rw-r--r-- | noncore/apps/opie-write/qtextedit.cpp | 4516 | ||||
-rw-r--r-- | noncore/apps/opie-write/qtextedit.h | 448 |
15 files changed, 18815 insertions, 0 deletions
diff --git a/noncore/apps/opie-write/main.cpp b/noncore/apps/opie-write/main.cpp new file mode 100644 index 0000000..027af38 --- a/dev/null +++ b/noncore/apps/opie-write/main.cpp @@ -0,0 +1,37 @@ +/********************************************************************** +** Copyright (C) 2000-2002 Trolltech AS. All rights reserved. +** +** This file is part of the Qtopia Environment. +** +** Licensees holding valid Qtopia Developer license may use this +** file in accordance with the Qtopia Developer License Agreement +** provided with the Software. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING +** THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +** PURPOSE. +** +** email sales@trolltech.com for information about Qtopia License +** Agreements. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#include <qpe/qpeapplication.h> +#include <qpe/fileselector.h> +#include "mainwindow.h" + +int main( int argc, char ** argv ) +{ + QPEApplication a( argc, argv ); + + MainWindow e; + a.showMainDocumentWidget(&e); + + QObject::connect( &a, SIGNAL( lastWindowClosed() ), + &a, SLOT( quit() ) ); + + a.exec(); +} diff --git a/noncore/apps/opie-write/mainwindow.cpp b/noncore/apps/opie-write/mainwindow.cpp new file mode 100644 index 0000000..ed95e83 --- a/dev/null +++ b/noncore/apps/opie-write/mainwindow.cpp @@ -0,0 +1,570 @@ +/********************************************************************** +** Copyright (C) 2000-2002 Trolltech AS. All rights reserved. +** +** This file is part of the Qtopia Environment. +** +** Licensees holding valid Qtopia Developer license may use this +** file in accordance with the Qtopia Developer License Agreement +** provided with the Software. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING +** THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +** PURPOSE. +** +** email sales@trolltech.com for information about Qtopia License +** Agreements. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#include "mainwindow.h" +#include <qpe/fileselector.h> +#include <qpe/applnk.h> +#include <qpe/resource.h> +//#include "qspellchecker.h" +#include "qtextedit.h" +#include <qaction.h> +#include <qtoolbar.h> +#include <qtoolbutton.h> +#include <qtabwidget.h> +#include <qapplication.h> +#include <qfontdatabase.h> +#include <qcombobox.h> +#include <qlineedit.h> +#include <qfileinfo.h> +#include <qfile.h> +#include <qfiledialog.h> +#include <qprinter.h> +#include <qpaintdevicemetrics.h> +#include <qmenubar.h> +#include <qpopupmenu.h> +#include <qcolordialog.h> +#include <qpainter.h> +#include <qstyle.h> + +class ButtonMenu : public QToolButton +{ + Q_OBJECT +public: + ButtonMenu( QWidget *parent, const char *name=0 ) + : QToolButton( parent, name ), current(0) + { + setPopup( new QPopupMenu( this ) ); + setPopupDelay( 1 ); + connect( popup(), SIGNAL(activated(int)), this, SLOT(selected(int)) ); + } + + int insertItem(const QIconSet &icon, const QString &text, int id ) { + if ( !popup()->count() ) { + setIconSet( icon ); + current = id; + } + return popup()->insertItem( icon, text, id ); + } + + void setCurrentItem( int id ) { + if ( id != current ) { + current = id; + setIconSet( *popup()->iconSet( id ) ); + } + } + + virtual QSize sizeHint() const { + return QToolButton::sizeHint() + QSize( 4, 0 ); + } + +signals: + void activated( int id ); + +protected slots: + void selected( int id ) { + current = id; + setIconSet( *popup()->iconSet( id ) ); + emit activated( id ); + } + +protected: + virtual void drawButtonLabel( QPainter *p ) { + p->translate( -4, 0 ); + QToolButton::drawButtonLabel( p ); + p->translate( 4, 0 ); + } + +private: + int current; +}; + +//=========================================================================== + +MainWindow::MainWindow( QWidget *parent, const char *name ) + : QMainWindow( parent, name ), + doc( 0 ) +{ + setRightJustification(TRUE); + + editorStack = new QWidgetStack( this ); + + fileSelector = new FileSelector( "text/html", + editorStack, "fileselector" ); + + + fileSelector->setCloseVisible( FALSE ); + editorStack->addWidget( fileSelector, 0 ); + + editor = new Qt3::QTextEdit( editorStack ); + editor->setTextFormat( Qt::RichText ); + editorStack->addWidget( editor, 1 ); + + setupActions(); + + QObject::connect( fileSelector, SIGNAL(closeMe()), + this, SLOT(showEditTools()) ); + QObject::connect( fileSelector, SIGNAL(fileSelected(const DocLnk &)), + this, SLOT(openFile(const DocLnk &)) ); + QObject::connect( fileSelector, SIGNAL(newSelected(const DocLnk&)), + this, SLOT(newFile(const DocLnk&)) ); + + if ( fileSelector->fileCount() < 1 ) + fileNew(); + else { + fileOpen(); + } + doConnections( editor ); + + setCentralWidget( editorStack ); +} + +MainWindow::~MainWindow() +{ + save(); +} + +void MainWindow::setupActions() +{ + setToolBarsMovable(false); + + tbMenu = new QToolBar( this ); + tbMenu->setHorizontalStretchable( TRUE ); + + QMenuBar *menu = new QMenuBar( tbMenu ); + + tbEdit = new QToolBar( this ); + + QPopupMenu *file = new QPopupMenu( this ); + menu->insertItem( tr("File"), file ); + + QPopupMenu *edit = new QPopupMenu( this ); + menu->insertItem( tr("Edit"), edit ); + + // ### perhaps these shortcut keys should have some + // IPaq keys defined??? + QAction *a; + + a = new QAction( tr( "New" ), Resource::loadPixmap("new"), QString::null, 0, this, 0 ); + connect( a, SIGNAL(activated()), this, SLOT(fileNew()) ); + a->addTo( file ); + + a = new QAction( tr( "Open" ), Resource::loadPixmap( "fileopen" ), QString::null, 0, this, 0 ); + connect( a, SIGNAL(activated()), this, SLOT(fileOpen()) ); + a->addTo( file ); + + a = new QAction( tr( "Undo" ), Resource::loadIconSet("opie-write/undo"), + QString::null, 0, this, "editUndo" ); + connect( a, SIGNAL( activated() ), this, SLOT( editUndo() ) ); + connect( editor, SIGNAL(undoAvailable(bool)), a, SLOT(setEnabled(bool)) ); + a->addTo( tbEdit ); + a->addTo( edit ); + a = new QAction( tr( "Redo" ), Resource::loadIconSet("opie-write/redo"), + QString::null, 0, this, "editRedo" ); + connect( a, SIGNAL( activated() ), this, SLOT( editRedo() ) ); + connect( editor, SIGNAL(redoAvailable(bool)), a, SLOT(setEnabled(bool)) ); + a->addTo( tbEdit ); + a->addTo( edit ); + + edit->insertSeparator(); + + a = new QAction( tr( "Copy" ), Resource::loadIconSet("copy"), + QString::null, 0, this, "editCopy" ); + connect( a, SIGNAL( activated() ), this, SLOT( editCopy() ) ); + connect( editor, SIGNAL(copyAvailable(bool)), a, SLOT(setEnabled(bool)) ); + a->addTo( tbEdit ); + a->addTo( edit ); + a = new QAction( tr( "Cut" ), Resource::loadIconSet("cut"), + QString::null, 0, this, "editCut" ); + connect( a, SIGNAL( activated() ), this, SLOT( editCut() ) ); + connect( editor, SIGNAL(copyAvailable(bool)), a, SLOT(setEnabled(bool)) ); + a->addTo( tbEdit ); + a->addTo( edit ); + a = new QAction( tr( "Paste" ), Resource::loadPixmap("paste"), + QString::null, 0, this, "editPaste" ); + connect( a, SIGNAL( activated() ), this, SLOT( editPaste() ) ); + a->addTo( tbEdit ); + a->addTo( edit ); + + tbFont = new QToolBar( this ); + tbFont->setLabel( "Font Actions" ); + tbFont->setHorizontalStretchable(TRUE); + + comboFont = new QComboBox( FALSE, tbFont ); + QFontDatabase db; + comboFont->insertStringList( db.families() ); + connect( comboFont, SIGNAL( activated( const QString & ) ), + this, SLOT( textFamily( const QString & ) ) ); + comboFont->setCurrentItem( comboFont->listBox()->index( comboFont->listBox()->findItem( QApplication::font().family() ) ) ); + + comboSize = new QComboBox( TRUE, tbFont ); + QValueList<int> sizes = db.standardSizes(); + QValueList<int>::Iterator it = sizes.begin(); + for ( ; it != sizes.end(); ++it ) + comboSize->insertItem( QString::number( *it ) ); + connect( comboSize, SIGNAL( activated( const QString & ) ), + this, SLOT( textSize( const QString & ) ) ); + comboSize->lineEdit()->setText( QString::number( QApplication::font().pointSize() ) ); + comboSize->setFixedWidth( 38 ); + + tbStyle = new QToolBar( this ); + tbStyle->setLabel( "Style Actions" ); + + actionTextBold = new QAction( tr( "Bold" ), + Resource::loadPixmap("bold"), + QString::null, CTRL + Key_B, + this, "textBold" ); + connect( actionTextBold, SIGNAL( activated() ), this, SLOT( textBold() ) ); + actionTextBold->addTo( tbStyle ); + actionTextBold->setToggleAction( TRUE ); + actionTextItalic = new QAction( tr( "Italic" ), + Resource::loadPixmap("italic"), + tr( "&Italic" ), CTRL + Key_I, + this, "textItalic" ); + connect( actionTextItalic, SIGNAL( activated() ), this, + SLOT( textItalic() ) ); + actionTextItalic->addTo( tbStyle ); + actionTextItalic->setToggleAction( TRUE ); + actionTextUnderline = new QAction( tr( "Underline" ), + Resource::loadPixmap("underline"), + tr( "&Underline" ), CTRL + Key_U, + this, "textUnderline" ); + connect( actionTextUnderline, SIGNAL( activated() ), + this, SLOT( textUnderline() ) ); + actionTextUnderline->addTo( tbStyle ); + actionTextUnderline->setToggleAction( TRUE ); + + alignMenu = new ButtonMenu( tbStyle ); + alignMenu->insertItem( Resource::loadPixmap("left"), tr("Left"), AlignLeft ); + alignMenu->insertItem( Resource::loadPixmap("center"), tr("Center"), AlignCenter ); + alignMenu->insertItem( Resource::loadPixmap("right"), tr("Right"), AlignRight ); + alignMenu->insertItem( Resource::loadPixmap("opie-write/justify"), tr("Full"), Qt3::AlignJustify ); + connect( alignMenu, SIGNAL(activated(int)), this, SLOT(textAlign(int)) ); +} + +Qt3::QTextEdit *MainWindow::currentEditor() const +{ + return editor; +} + +void MainWindow::doConnections( Qt3::QTextEdit *e ) +{ + connect( e, SIGNAL( currentFontChanged( const QFont & ) ), + this, SLOT( fontChanged( const QFont & ) ) ); + connect( e, SIGNAL( currentColorChanged( const QColor & ) ), + this, SLOT( colorChanged( const QColor & ) ) ); + connect( e, SIGNAL( currentAlignmentChanged( int ) ), + this, SLOT( alignmentChanged( int ) ) ); +} + +void MainWindow::updateFontSizeCombo( const QFont &f ) +{ + comboSize->clear(); + QFontDatabase fdb; + QValueList<int> sizes = fdb.pointSizes( f.family() ); + QValueList<int>::Iterator it = sizes.begin(); + for ( ; it != sizes.end(); ++it ) + comboSize->insertItem( QString::number( *it ) ); +} + +void MainWindow::editUndo() +{ + if ( !currentEditor() ) + return; + currentEditor()->undo(); +} + +void MainWindow::editRedo() +{ + if ( !currentEditor() ) + return; + currentEditor()->redo(); +} + +void MainWindow::editCut() +{ + if ( !currentEditor() ) + return; + currentEditor()->cut(); +} + +void MainWindow::editCopy() +{ + if ( !currentEditor() ) + return; + currentEditor()->copy(); +} + +void MainWindow::editPaste() +{ + if ( !currentEditor() ) + return; + currentEditor()->paste(); +} + +void MainWindow::textBold() +{ + if ( !currentEditor() ) + return; + currentEditor()->setBold( actionTextBold->isOn() ); +} + +void MainWindow::textUnderline() +{ + if ( !currentEditor() ) + return; + currentEditor()->setUnderline( actionTextUnderline->isOn() ); +} + +void MainWindow::textItalic() +{ + if ( !currentEditor() ) + return; + currentEditor()->setItalic( actionTextItalic->isOn() ); +} + +void MainWindow::textFamily( const QString &f ) +{ + if ( !currentEditor() ) + return; + currentEditor()->setFamily( f ); + currentEditor()->viewport()->setFocus(); +} + +void MainWindow::textSize( const QString &p ) +{ + if ( !currentEditor() ) + return; + currentEditor()->setPointSize( p.toInt() ); + currentEditor()->viewport()->setFocus(); +} + +void MainWindow::textStyle( int i ) +{ + if ( !currentEditor() ) + return; + if ( i == 0 ) + currentEditor()->setParagType( Qt3::QStyleSheetItem::DisplayBlock, + Qt3::QStyleSheetItem::ListDisc ); + else if ( i == 1 ) + currentEditor()->setParagType( Qt3::QStyleSheetItem::DisplayListItem, + Qt3::QStyleSheetItem::ListDisc ); + else if ( i == 2 ) + currentEditor()->setParagType( Qt3::QStyleSheetItem::DisplayListItem, + Qt3::QStyleSheetItem::ListCircle ); + else if ( i == 3 ) + currentEditor()->setParagType( Qt3::QStyleSheetItem::DisplayListItem, + Qt3::QStyleSheetItem::ListSquare ); + else if ( i == 4 ) + currentEditor()->setParagType( Qt3::QStyleSheetItem::DisplayListItem, + Qt3::QStyleSheetItem::ListDecimal ); + else if ( i == 5 ) + currentEditor()->setParagType( Qt3::QStyleSheetItem::DisplayListItem, + Qt3::QStyleSheetItem::ListLowerAlpha ); + else if ( i == 6 ) + currentEditor()->setParagType( Qt3::QStyleSheetItem::DisplayListItem, + Qt3::QStyleSheetItem::ListUpperAlpha ); + currentEditor()->viewport()->setFocus(); +} + +void MainWindow::textAlign( int a ) +{ + if ( !currentEditor() ) + return; + editor->setAlignment( a ); +} + +void MainWindow::fontChanged( const QFont &f ) +{ + comboFont->setCurrentItem( comboFont->listBox()->index( comboFont->listBox()->findItem( f.family() ) ) ); + updateFontSizeCombo( f ); + comboSize->lineEdit()->setText( QString::number( f.pointSize() ) ); + actionTextBold->setOn( f.bold() ); + actionTextItalic->setOn( f.italic() ); + actionTextUnderline->setOn( f.underline() ); +} + +void MainWindow::colorChanged( const QColor & ) +{ +} + +void MainWindow::alignmentChanged( int a ) +{ + if ( ( a == Qt3::AlignAuto ) || ( a & AlignLeft )) { + alignMenu->setCurrentItem(AlignLeft); + } else if ( ( a & AlignCenter ) ) { + alignMenu->setCurrentItem(AlignCenter); + } else if ( ( a & AlignRight ) ) { + alignMenu->setCurrentItem(AlignRight); + } else if ( ( a & Qt3::AlignJustify ) ) { + alignMenu->setCurrentItem(Qt3::AlignJustify); + } +} + +void MainWindow::editorChanged( QWidget * ) +{ + if ( !currentEditor() ) + return; + fontChanged( currentEditor()->font() ); + colorChanged( currentEditor()->color() ); + alignmentChanged( currentEditor()->alignment() ); +} + +void MainWindow::fileOpen() +{ + save(); + editorStack->raiseWidget( fileSelector ); + fileSelector->reread(); + hideEditTools(); + fileSelector->setNewVisible( TRUE ); + clear(); + updateCaption(); +} + +void MainWindow::fileRevert() +{ + qDebug( "QMainWindow::fileRevert needs to be done" ); +} + +void MainWindow::fileNew() +{ + editor->setTextFormat( Qt::RichText ); + save(); + newFile(DocLnk()); +} + +void MainWindow::insertTable() +{ + qDebug( "MainWindow::insertTable() needs to be done" ); +} + +void MainWindow::newFile( const DocLnk &dl ) +{ + DocLnk nf = dl; + nf.setType( "text/html" ); + clear(); + editorStack->raiseWidget( editor ); + editor->viewport()->setFocus(); + doc = new DocLnk( nf ); + updateCaption(); +} + +void MainWindow::openFile( const DocLnk &dl ) +{ + FileManager fm; + QString txt; + if ( !fm.loadFile( dl, txt ) ) + qDebug( "couldn't open file" ); + clear(); + editorStack->raiseWidget( editor ); + editor->viewport()->setFocus(); + doc = new DocLnk( dl ); + editor->setText( txt ); + editor->setModified( FALSE ); + updateCaption(); +} + +void MainWindow::showEditTools( void ) +{ + tbMenu->show(); + tbEdit->show(); + tbFont->show(); + tbStyle->show(); +} + +void MainWindow::hideEditTools( void ) +{ + // let's reset the buttons... + actionTextBold->setOn( FALSE ); + actionTextItalic->setOn( FALSE ); + actionTextUnderline->setOn( FALSE ); + //comboFont->setCurrentText( QApplication::font().family() ); + comboSize->lineEdit()->setText( QString::number(QApplication::font().pointSize() ) ); + tbMenu->hide(); + tbEdit->hide(); + tbFont->hide(); + tbStyle->hide(); +} + + +void MainWindow::save() +{ + if ( !doc ) + return; + if ( !editor->isModified() ) + return; + + QString rt = editor->text(); + + // quick hack to get around formatting... + editor->setTextFormat( Qt::PlainText ); + QString pt = editor->text(); + editor->setTextFormat( Qt::RichText ); + + if ( doc->name().isEmpty() ) { + unsigned ispace = pt.find( ' ' ); + unsigned ienter = pt.find( '\n' ); + int i = (ispace < ienter) ? ispace : ienter; + QString docname; + if ( i == -1 ) { + if ( pt.isEmpty() ) + docname = "Empty Text"; + else + docname = pt; + } else { + docname = pt.left( i ); + } + doc->setName(docname); + } + FileManager fm; + fm.saveFile( *doc, rt ); +} + +void MainWindow::clear() +{ + delete doc; + doc = 0; + editor->clear(); +} + +void MainWindow::updateCaption() +{ + if ( !doc ) + setCaption( tr("Rich Text Editor") ); + else { + QString s = doc->name(); + if ( s.isEmpty() ) + s = tr( "Unnamed" ); + setCaption( s + " - " + tr("Rich Text Editor") ); + } +} + +void MainWindow::closeEvent( QCloseEvent *e ) +{ + if ( editorStack->visibleWidget() == editor ) { + // call fileOpen instead, don't close it + fileOpen(); + e->ignore(); + } else { + e->accept(); + } +} + +#include "mainwindow.moc" diff --git a/noncore/apps/opie-write/mainwindow.h b/noncore/apps/opie-write/mainwindow.h new file mode 100644 index 0000000..565ad05 --- a/dev/null +++ b/noncore/apps/opie-write/mainwindow.h @@ -0,0 +1,113 @@ +/********************************************************************** +** Copyright (C) 2000-2002 Trolltech AS. All rights reserved. +** +** This file is part of the Qtopia Environment. +** +** Licensees holding valid Qtopia Developer license may use this +** file in accordance with the Qtopia Developer License Agreement +** provided with the Software. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING +** THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +** PURPOSE. +** +** email sales@trolltech.com for information about Qtopia License +** Agreements. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <qmainwindow.h> +#include <qwidgetstack.h> +#include <qmap.h> +#include <qpe/filemanager.h> + +class QAction; +class QComboBox; +class FileSelectorView; +class FileSelector; +class QToolBar; +class ButtonMenu; + +namespace Qt3 { + +class QTextEdit; + +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow( QWidget *parent = 0, const char *name = 0 ); + ~MainWindow(); + +protected: + void closeEvent( QCloseEvent *e ); + +private slots: + // new file functions + void fileOpen(); + void fileRevert(); + void fileNew(); + + void editUndo(); + void editRedo(); + void editCut(); + void editCopy(); + void editPaste(); + + void textBold(); + void textUnderline(); + void textItalic(); + void textFamily( const QString &f ); + void textSize( const QString &p ); + void textStyle( int s ); + void textAlign( int ); + + void fontChanged( const QFont &f ); + void colorChanged( const QColor &c ); + void alignmentChanged( int a ); + void editorChanged( QWidget * ); + + // these are from textedit, we may need them + void insertTable(); + void newFile( const DocLnk & ); + void openFile( const DocLnk & ); + void showEditTools(); + void hideEditTools(); + +private: + void updateFontSizeCombo( const QFont &f ); + void setupActions(); + Qt3::QTextEdit *currentEditor() const; + void doConnections( Qt3::QTextEdit *e ); + void updateCaption(); + void save(); + void clear(); + + // added these from the textedit + QWidgetStack *editorStack; + FileSelector *fileSelector; + QToolBar *tbMenu; + QToolBar *tbEdit; + QToolBar *tbFont; + QToolBar *tbStyle; + QAction *actionTextBold, + *actionTextUnderline, + *actionTextItalic; + QComboBox *comboFont, + *comboSize; + ButtonMenu *alignMenu; + DocLnk *doc; + Qt3::QTextEdit* editor; +}; + + +#endif diff --git a/noncore/apps/opie-write/opie-write.pro b/noncore/apps/opie-write/opie-write.pro new file mode 100644 index 0000000..e2ce3a9 --- a/dev/null +++ b/noncore/apps/opie-write/opie-write.pro @@ -0,0 +1,25 @@ +TEMPLATE = app +CONFIG += qt warn_on release + +DESTDIR = $(OPIEDIR)/bin + +HEADERS = qcleanuphandler.h \ + qcomplextext_p.h \ + qrichtext_p.h \ + qstylesheet.h \ + qtextedit.h \ + mainwindow.h + +SOURCES = qcomplextext.cpp \ + qstylesheet.cpp \ + qrichtext_p.cpp \ + qrichtext.cpp \ + qtextedit.cpp \ + main.cpp \ + mainwindow.cpp + +INCLUDEPATH += $(OPIEDIR)/include +DEPENDPATH += $(OPIEDIR)/include +LIBS += -lqpe + +TARGET = opie-write diff --git a/noncore/apps/opie-write/qcleanuphandler.h b/noncore/apps/opie-write/qcleanuphandler.h new file mode 100644 index 0000000..5c5bf16 --- a/dev/null +++ b/noncore/apps/opie-write/qcleanuphandler.h @@ -0,0 +1,139 @@ +/**************************************************************************** +** $Id$ +** +** ... +** +** Copyright (C) 2001-2002 Trolltech AS. All rights reserved. +** +** This file is part of the tools module of the Qt GUI Toolkit. +** +** This file may be distributed under the terms of the Q Public License +** as defined by Trolltech AS of Norway and appearing in the file +** LICENSE.QPL included in the packaging of this file. +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition +** licenses may use this file in accordance with the Qt Commercial License +** Agreement provided with the Software. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for +** information about Qt Commercial License Agreements. +** See http://www.trolltech.com/qpl/ for QPL licensing information. +** See http://www.trolltech.com/gpl/ for GPL licensing information. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#ifndef QCLEANUPHANDLER_H +#define QCLEANUPHANDLER_H + +#ifndef QT_H +#include <qlist.h> +#endif // QT_H + +template<class Type> +#ifdef Q_NO_TEMPLATE_EXPORT +class QCleanupHandler +#else +class Q_EXPORT QCleanupHandler +#endif +{ +public: + QCleanupHandler() : cleanupObjects( 0 ) {} + ~QCleanupHandler() { clear(); } + + Type* add( Type **object ) { + if ( !cleanupObjects ) + cleanupObjects = new QPtrList<Type*>; + cleanupObjects->insert( 0, object ); + return *object; + } + + void remove( Type **object ) { + if ( !cleanupObjects ) + return; + if ( cleanupObjects->findRef( object ) >= 0 ) + (void) cleanupObjects->take(); + } + + bool isEmpty() const { + return cleanupObjects ? cleanupObjects->isEmpty() : TRUE; + } + + void clear() { + if ( !cleanupObjects ) + return; + QPtrListIterator<Type*> it( *cleanupObjects ); + Type **object; + while ( ( object = it.current() ) ) { + delete *object; + *object = 0; + cleanupObjects->remove( object ); + } + delete cleanupObjects; + cleanupObjects = 0; + } + +private: + QPtrList<Type*> *cleanupObjects; +}; + +template<class Type> +#ifdef Q_NO_TEMPLATE_EXPORT +class QSingleCleanupHandler +#else +class Q_EXPORT QSingleCleanupHandler +#endif +{ +public: + QSingleCleanupHandler() : object( 0 ) {} + ~QSingleCleanupHandler() { + if ( object ) { + delete *object; + *object = 0; + } + } + Type* set( Type **o ) { + object = o; + return *object; + } + void reset() { object = 0; } +private: + Type **object; +}; + +template<class Type> +#ifdef Q_NO_TEMPLATE_EXPORT +class QSharedCleanupHandler +#else +class Q_EXPORT QSharedCleanupHandler +#endif +{ +public: + QSharedCleanupHandler() : object( 0 ) {} + ~QSharedCleanupHandler() { + if ( object ) { + if ( (*object)->deref() ) + delete *object; + *object = 0; + } + } + Type* set( Type **o ) { + object = o; + return *object; + } + void reset() { object = 0; } +private: + Type **object; +}; + +#endif //QCLEANUPHANDLER_H diff --git a/noncore/apps/opie-write/qcomplextext.cpp b/noncore/apps/opie-write/qcomplextext.cpp new file mode 100644 index 0000000..0fa6c2e --- a/dev/null +++ b/noncore/apps/opie-write/qcomplextext.cpp @@ -0,0 +1,152 @@ +/**************************************************************************** +** $Id$ +** +** Implementation of some internal classes +** +** Created : +** +** Copyright (C) 2001 Trolltech AS. All rights reserved. +** +** This file is part of the kernel module of the Qt GUI Toolkit. +** +** This file may be distributed under the terms of the Q Public License +** as defined by Trolltech AS of Norway and appearing in the file +** LICENSE.QPL included in the packaging of this file. +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition +** licenses may use this file in accordance with the Qt Commercial License +** Agreement provided with the Software. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for +** information about Qt Commercial License Agreements. +** See http://www.trolltech.com/qpl/ for QPL licensing information. +** See http://www.trolltech.com/gpl/ for GPL licensing information. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#include "qcomplextext_p.h" + +#include "qrichtext_p.h" +#include "qfontmetrics.h" +#include "qrect.h" + +#include <stdlib.h> + +using namespace Qt3; + +// ----------------------------------------------------- + +/* a small helper class used internally to resolve Bidi embedding levels. + Each line of text caches the embedding level at the start of the line for faster + relayouting +*/ +QBidiContext::QBidiContext( uchar l, QChar::Direction e, QBidiContext *p, bool o ) + : level(l) , override(o), dir(e) +{ + if ( p ) + p->ref(); + parent = p; + count = 0; +} + +QBidiContext::~QBidiContext() +{ + if( parent && parent->deref() ) + delete parent; +} + +static QChar *shapeBuffer = 0; +static int shapeBufSize = 0; + +/* + Arabic shaping obeys a number of rules according to the joining classes (see Unicode book, section on + arabic). + + Each unicode char has a joining class (right, dual (left&right), center (joincausing) or transparent). + transparent joining is not encoded in QChar::joining(), but applies to all combining marks and format marks. + + Right join-causing: dual + center + Left join-causing: dual + right + center + + Rules are as follows (for a string already in visual order, as we have it here): + + R1 Transparent characters do not affect joining behaviour. + R2 A right joining character, that has a right join-causing char on the right will get form XRight + (R3 A left joining character, that has a left join-causing char on the left will get form XLeft) + Note: the above rule is meaningless, as there are no pure left joining characters defined in Unicode + R4 A dual joining character, that has a left join-causing char on the left and a right join-causing char on + the right will get form XMedial + R5 A dual joining character, that has a right join causing char on the right, and no left join causing char on the left + will get form XRight + R6 A dual joining character, that has a left join causing char on the left, and no right join causing char on the right + will get form XLeft + R7 Otherwise the character will get form XIsolated + + Additionally we have to do the minimal ligature support for lam-alef ligatures: + + L1 Transparent characters do not affect ligature behaviour. + L2 Any sequence of Alef(XRight) + Lam(XMedial) will form the ligature Alef.Lam(XLeft) + L3 Any sequence of Alef(XRight) + Lam(XLeft) will form the ligature Alef.Lam(XIsolated) + + The two functions defined in this class do shaping in visual and logical order. For logical order just replace right with + previous and left with next in the above rules ;-) +*/ + +/* + Two small helper functions for arabic shaping. They get the next shape causing character on either + side of the char in question. Implements rule R1. + + leftChar() returns true if the char to the left is a left join-causing char + rightChar() returns true if the char to the right is a right join-causing char +*/ +static inline const QChar *prevChar( const QString &str, int pos ) +{ + //qDebug("leftChar: pos=%d", pos); + pos--; + const QChar *ch = str.unicode() + pos; + while( pos > -1 ) { + if( !ch->isMark() ) + return ch; + pos--; + ch--; + } + return &QChar::replacement; +} + +static inline const QChar *nextChar( const QString &str, int pos) +{ + pos++; + int len = str.length(); + const QChar *ch = str.unicode() + pos; + while( pos < len ) { + //qDebug("rightChar: %d isLetter=%d, joining=%d", pos, ch.isLetter(), ch.joining()); + if( !ch->isMark() ) + return ch; + // assume it's a transparent char, this might not be 100% correct + pos++; + ch++; + } + return &QChar::replacement; +} + +static inline bool prevVisualCharJoins( const QString &str, int pos) +{ + return ( prevChar( str, pos )->joining() != QChar::OtherJoining ); +} + +static inline bool nextVisualCharJoins( const QString &str, int pos) +{ + QChar::Joining join = nextChar( str, pos )->joining(); + return ( join == QChar::Dual || join == QChar::Center ); +} diff --git a/noncore/apps/opie-write/qcomplextext_p.h b/noncore/apps/opie-write/qcomplextext_p.h new file mode 100644 index 0000000..b2d8293 --- a/dev/null +++ b/noncore/apps/opie-write/qcomplextext_p.h @@ -0,0 +1,133 @@ +/**************************************************************************** +** $Id$ +** +** Internal header file. +** +** Created : +** +** Copyright (C) 2001 Trolltech AS. All rights reserved. +** +** This file is part of the kernel module of the Qt GUI Toolkit. +** +** This file may be distributed under the terms of the Q Public License +** as defined by Trolltech AS of Norway and appearing in the file +** LICENSE.QPL included in the packaging of this file. +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition +** licenses may use this file in accordance with the Qt Commercial License +** Agreement provided with the Software. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for +** information about Qt Commercial License Agreements. +** See http://www.trolltech.com/qpl/ for QPL licensing information. +** See http://www.trolltech.com/gpl/ for GPL licensing information. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#ifndef QCOMPLEXTEXT_H +#define QCOMPLEXTEXT_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Remote Control. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// +// + +#ifndef QT_H +#include "qt3namespace.h" +#include <qstring.h> +#include <qpointarray.h> +#include <qfont.h> +#include <qpainter.h> +#include <qlist.h> +#include <qshared.h> +#endif // QT_H + +class QFontPrivate; + +namespace Qt3 { + +// bidi helper classes. Internal to Qt +struct Q_EXPORT QBidiStatus { + QBidiStatus() { + eor = QChar::DirON; + lastStrong = QChar::DirON; + last = QChar:: DirON; + } + QChar::Direction eor; + QChar::Direction lastStrong; + QChar::Direction last; +}; + +struct Q_EXPORT QBidiContext : public QShared { + // ### ref and deref parent? + QBidiContext( uchar level, QChar::Direction embedding, QBidiContext *parent = 0, bool override = FALSE ); + ~QBidiContext(); + + unsigned char level; + bool override : 1; + QChar::Direction dir : 5; + + QBidiContext *parent; +}; + +struct Q_EXPORT QBidiControl { + QBidiControl() { context = 0; } + QBidiControl( QBidiContext *c, QBidiStatus s) + { context = c; if( context ) context->ref(); status = s; } + ~QBidiControl() { if ( context && context->deref() ) delete context; } + void setContext( QBidiContext *c ) { if ( context == c ) return; if ( context && context->deref() ) delete context; context = c; context->ref(); } + QBidiContext *context; + QBidiStatus status; +}; + +struct Q_EXPORT QTextRun { + QTextRun(int _start, int _stop, QBidiContext *context, QChar::Direction dir); + + int start; + int stop; + // explicit + implicit levels here + uchar level; +}; + +class Q_EXPORT QComplexText { +public: + enum Shape { + XIsolated, + XFinal, + XInitial, + XMedial + }; + static Shape glyphVariant( const QString &str, int pos); + static Shape glyphVariantLogical( const QString &str, int pos); + + static QChar shapedCharacter(const QString &str, int pos, const QFontMetrics *fm = 0); + + // positions non spacing marks relative to the base character at position pos. + static QPointArray positionMarks( QFontPrivate *f, const QString &str, int pos, QRect *boundingRect = 0 ); + + static QPtrList<QTextRun> *bidiReorderLine( QBidiControl *control, const QString &str, int start, int len, + QChar::Direction basicDir = QChar::DirON ); + static QString bidiReorderString( const QString &str, QChar::Direction basicDir = QChar::DirON ); +}; + +} // namespace Qt3 + +#endif diff --git a/noncore/apps/opie-write/qrichtext.cpp b/noncore/apps/opie-write/qrichtext.cpp new file mode 100644 index 0000000..7901000 --- a/dev/null +++ b/noncore/apps/opie-write/qrichtext.cpp @@ -0,0 +1,8085 @@ +/**************************************************************************** +** $Id$ +** +** Implementation of the internal Qt classes dealing with rich text +** +** Created : 990101 +** +** Copyright (C) 1992-2000 Trolltech AS. All rights reserved. +** +** This file is part of the kernel module of the Qt GUI Toolkit. +** +** This file may be distributed under the terms of the Q Public License +** as defined by Trolltech AS of Norway and appearing in the file +** LICENSE.QPL included in the packaging of this file. +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition +** licenses may use this file in accordance with the Qt Commercial License +** Agreement provided with the Software. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for +** information about Qt Commercial License Agreements. +** See http://www.trolltech.com/qpl/ for QPL licensing information. +** See http://www.trolltech.com/gpl/ for GPL licensing information. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#include "qrichtext_p.h" + +#include "qstringlist.h" +#include "qfont.h" +#include "qtextstream.h" +#include "qfile.h" +#include "qregexp.h" +#include "qapplication.h" +#include "qclipboard.h" +#include "qmap.h" +#include "qfileinfo.h" +#include "qstylesheet.h" +#include "qmime.h" +#include "qregexp.h" +#include "qimage.h" +#include "qdragobject.h" +#include "qpaintdevicemetrics.h" +#include "qpainter.h" +#include "qdrawutil.h" +#include "qcursor.h" +#include "qstack.h" +#include "qstyle.h" +#include "qcomplextext_p.h" +#include "qcleanuphandler.h" + +#include <stdlib.h> + +using namespace Qt3; + +//#define PARSER_DEBUG +//#define DEBUG_COLLECTION// ---> also in qrichtext_p.h +//#define DEBUG_TABLE_RENDERING + +static QTextFormatCollection *qFormatCollection = 0; + +const int QStyleSheetItem_WhiteSpaceNoCompression = 3; // ### belongs in QStyleSheetItem, fix 3.1 +const int QStyleSheetItem_WhiteSpaceNormalWithNewlines = 4; // ### belongs in QStyleSheetItem, fix 3.1 + +const int border_tolerance = 2; + +#if defined(PARSER_DEBUG) +static QString debug_indent; +#endif + +#ifdef Q_WS_WIN +#include "qt_windows.h" +#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 ) ) { + QPaintDeviceMetrics metrics( painter->device() ); +#if defined(Q_WS_X11) + value = value * metrics.logicalDpiY() / QPaintDevice::x11AppDpiY(); +#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; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +void QTextCommandHistory::addCommand( QTextCommand *cmd ) +{ + if ( current < (int)history.count() - 1 ) { + QPtrList<QTextCommand> commands; + commands.setAutoDelete( FALSE ); + + for( int i = 0; i <= current; ++i ) { + commands.insert( i, history.at( 0 ) ); + history.take( 0 ); + } + + commands.append( cmd ); + history.clear(); + history = commands; + history.setAutoDelete( TRUE ); + } else { + history.append( cmd ); + } + + if ( (int)history.count() > steps ) + history.removeFirst(); + else + ++current; +} + +QTextCursor *QTextCommandHistory::undo( QTextCursor *c ) +{ + if ( current > -1 ) { + QTextCursor *c2 = history.at( current )->unexecute( c ); + --current; + return c2; + } + return 0; +} + +QTextCursor *QTextCommandHistory::redo( QTextCursor *c ) +{ + if ( current > -1 ) { + if ( current < (int)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 QTextCommandHistory::isUndoAvailable() +{ + return current > -1; +} + +bool QTextCommandHistory::isRedoAvailable() +{ + return current > -1 && current < (int)history.count() - 1 || current == -1 && history.count() > 0; +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextDeleteCommand::QTextDeleteCommand( QTextDocument *d, int i, int idx, const QMemArray<QTextStringChar> &str, + const QValueList< QPtrVector<QStyleSheetItem> > &os, + const QValueList<QStyleSheetItem::ListStyle> &ols, + const QMemArray<int> &oas) + : QTextCommand( d ), id( i ), index( idx ), parag( 0 ), text( str ), oldStyles( os ), oldListStyles( ols ), oldAligns( oas ) +{ + for ( int j = 0; j < (int)text.size(); ++j ) { + if ( text[ j ].format() ) + text[ j ].format()->addRef(); + } +} + +QTextDeleteCommand::QTextDeleteCommand( QTextParag *p, int idx, const QMemArray<QTextStringChar> &str ) + : QTextCommand( 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(); + } +} + +QTextDeleteCommand::~QTextDeleteCommand() +{ + for ( int i = 0; i < (int)text.size(); ++i ) { + if ( text[ i ].format() ) + text[ i ].format()->removeRef(); + } + text.resize( 0 ); +} + +QTextCursor *QTextDeleteCommand::execute( QTextCursor *c ) +{ + QTextParag *s = doc ? doc->paragAt( id ) : parag; + if ( !s ) { + qWarning( "can't locate parag at %d, last parag: %d", id, doc->lastParag()->paragId() ); + return 0; + } + + cursor.setParag( s ); + cursor.setIndex( index ); + int len = text.size(); + if ( c ) + *c = cursor; + if ( doc ) { + doc->setSelectionStart( QTextDocument::Temp, &cursor ); + for ( int i = 0; i < len; ++i ) + cursor.gotoNextLetter(); + doc->setSelectionEnd( QTextDocument::Temp, &cursor ); + doc->removeSelectedText( QTextDocument::Temp, &cursor ); + if ( c ) + *c = cursor; + } else { + s->remove( index, len ); + } + + return c; +} + +QTextCursor *QTextDeleteCommand::unexecute( QTextCursor *c ) +{ + QTextParag *s = doc ? doc->paragAt( id ) : parag; + if ( !s ) { + qWarning( "can't locate parag at %d, last parag: %d", id, doc->lastParag()->paragId() ); + return 0; + } + + cursor.setParag( s ); + cursor.setIndex( index ); + QString str = QTextString::toString( text ); + cursor.insert( str, TRUE, &text ); + cursor.setParag( s ); + cursor.setIndex( index ); + if ( c ) { + c->setParag( s ); + c->setIndex( index ); + for ( int i = 0; i < (int)text.size(); ++i ) + c->gotoNextLetter(); + } + + QValueList< QPtrVector<QStyleSheetItem> >::Iterator it = oldStyles.begin(); + QValueList<QStyleSheetItem::ListStyle>::Iterator lit = oldListStyles.begin(); + int i = 0; + QTextParag *p = s; + bool end = FALSE; + while ( p ) { + if ( it != oldStyles.end() ) + p->setStyleSheetItems( *it ); + else + end = TRUE; + if ( lit != oldListStyles.end() ) + p->setListStyle( *lit ); + else + end = TRUE; + if ( i < (int)oldAligns.size() ) + p->setAlignment( oldAligns.at( i ) ); + else + end = TRUE; + if ( end ) + break; + p = p->next(); + ++it; + ++lit; + ++i; + } + + s = cursor.parag(); + while ( s ) { + s->format(); + s->setChanged( TRUE ); + if ( s == c->parag() ) + break; + s = s->next(); + } + + return &cursor; +} + +QTextFormatCommand::QTextFormatCommand( QTextDocument *d, int sid, int sidx, int eid, int eidx, + const QMemArray<QTextStringChar> &old, QTextFormat *f, int fl ) + : QTextCommand( d ), startId( sid ), startIndex( sidx ), endId( eid ), endIndex( eidx ), format( f ), oldFormats( old ), flags( fl ) +{ + format = d->formatCollection()->format( f ); + for ( int j = 0; j < (int)oldFormats.size(); ++j ) { + if ( oldFormats[ j ].format() ) + oldFormats[ j ].format()->addRef(); + } +} + +QTextFormatCommand::~QTextFormatCommand() +{ + format->removeRef(); + for ( int j = 0; j < (int)oldFormats.size(); ++j ) { + if ( oldFormats[ j ].format() ) + oldFormats[ j ].format()->removeRef(); + } +} + +QTextCursor *QTextFormatCommand::execute( QTextCursor *c ) +{ + QTextParag *sp = doc->paragAt( startId ); + QTextParag *ep = doc->paragAt( endId ); + if ( !sp || !ep ) + return c; + + QTextCursor start( doc ); + start.setParag( sp ); + start.setIndex( startIndex ); + QTextCursor end( doc ); + end.setParag( ep ); + end.setIndex( endIndex ); + + doc->setSelectionStart( QTextDocument::Temp, &start ); + doc->setSelectionEnd( QTextDocument::Temp, &end ); + doc->setFormat( QTextDocument::Temp, format, flags ); + doc->removeSelection( QTextDocument::Temp ); + if ( endIndex == ep->length() ) + end.gotoLeft(); + *c = end; + return c; +} + +QTextCursor *QTextFormatCommand::unexecute( QTextCursor *c ) +{ + QTextParag *sp = doc->paragAt( startId ); + QTextParag *ep = doc->paragAt( endId ); + if ( !sp || !ep ) + return 0; + + int idx = startIndex; + int fIndex = 0; + for ( ;; ) { + if ( oldFormats.at( fIndex ).c == '\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; + } + } + + QTextCursor end( doc ); + end.setParag( ep ); + end.setIndex( endIndex ); + if ( endIndex == ep->length() ) + end.gotoLeft(); + *c = end; + return c; +} + +QTextAlignmentCommand::QTextAlignmentCommand( QTextDocument *d, int fParag, int lParag, int na, const QMemArray<int> &oa ) + : QTextCommand( d ), firstParag( fParag ), lastParag( lParag ), newAlign( na ), oldAligns( oa ) +{ +} + +QTextCursor *QTextAlignmentCommand::execute( QTextCursor *c ) +{ + QTextParag *p = doc->paragAt( firstParag ); + if ( !p ) + return c; + while ( p ) { + p->setAlignment( newAlign ); + if ( p->paragId() == lastParag ) + break; + p = p->next(); + } + return c; +} + +QTextCursor *QTextAlignmentCommand::unexecute( QTextCursor *c ) +{ + QTextParag *p = doc->paragAt( firstParag ); + if ( !p ) + return c; + int i = 0; + while ( p ) { + if ( i < (int)oldAligns.size() ) + p->setAlignment( oldAligns.at( i ) ); + if ( p->paragId() == lastParag ) + break; + p = p->next(); + ++i; + } + return c; +} + +QTextParagTypeCommand::QTextParagTypeCommand( QTextDocument *d, int fParag, int lParag, bool l, + QStyleSheetItem::ListStyle s, const QValueList< QPtrVector<QStyleSheetItem> > &os, + const QValueList<QStyleSheetItem::ListStyle> &ols ) + : QTextCommand( d ), firstParag( fParag ), lastParag( lParag ), list( l ), listStyle( s ), oldStyles( os ), oldListStyles( ols ) +{ +} + +QTextCursor *QTextParagTypeCommand::execute( QTextCursor *c ) +{ + QTextParag *p = doc->paragAt( firstParag ); + if ( !p ) + return c; + while ( p ) { + p->setList( list, (int)listStyle ); + if ( p->paragId() == lastParag ) + break; + p = p->next(); + } + return c; +} + +QTextCursor *QTextParagTypeCommand::unexecute( QTextCursor *c ) +{ + QTextParag *p = doc->paragAt( firstParag ); + if ( !p ) + return c; + QValueList< QPtrVector<QStyleSheetItem> >::Iterator it = oldStyles.begin(); + QValueList<QStyleSheetItem::ListStyle>::Iterator lit = oldListStyles.begin(); + while ( p ) { + if ( it != oldStyles.end() ) + p->setStyleSheetItems( *it ); + if ( lit != oldListStyles.end() ) + p->setListStyle( *lit ); + if ( p->paragId() == lastParag ) + break; + p = p->next(); + ++it; + ++lit; + } + return c; +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextCursor::QTextCursor( QTextDocument *d ) + : doc( d ), ox( 0 ), oy( 0 ) +{ + nested = FALSE; + idx = 0; + string = doc ? doc->firstParag() : 0; + tmpIndex = -1; + valid = TRUE; +} + +QTextCursor::QTextCursor() +{ +} + +QTextCursor::QTextCursor( const QTextCursor &c ) +{ + doc = c.doc; + ox = c.ox; + oy = c.oy; + nested = c.nested; + idx = c.idx; + string = c.string; + tmpIndex = c.tmpIndex; + indices = c.indices; + parags = c.parags; + xOffsets = c.xOffsets; + yOffsets = c.yOffsets; + valid = c.valid; +} + +QTextCursor &QTextCursor::operator=( const QTextCursor &c ) +{ + doc = c.doc; + ox = c.ox; + oy = c.oy; + nested = c.nested; + idx = c.idx; + string = c.string; + tmpIndex = c.tmpIndex; + indices = c.indices; + parags = c.parags; + xOffsets = c.xOffsets; + yOffsets = c.yOffsets; + valid = c.valid; + + return *this; +} + +bool QTextCursor::operator==( const QTextCursor &c ) const +{ + return doc == c.doc && string == c.string && idx == c.idx; +} + +int QTextCursor::totalOffsetX() const +{ + if ( !nested ) + return 0; + QValueStack<int>::ConstIterator xit = xOffsets.begin(); + int xoff = ox; + for ( ; xit != xOffsets.end(); ++xit ) + xoff += *xit; + return xoff; +} + +int QTextCursor::totalOffsetY() const +{ + if ( !nested ) + return 0; + QValueStack<int>::ConstIterator yit = yOffsets.begin(); + int yoff = oy; + for ( ; yit != yOffsets.end(); ++yit ) + yoff += *yit; + return yoff; +} + +void QTextCursor::gotoIntoNested( const QPoint &globalPos ) +{ + if ( !doc ) + return; + push(); + ox = 0; + int bl, y; + string->lineHeightOfChar( idx, &bl, &y ); + oy = y + string->rect().y(); + nested = TRUE; + QPoint p( globalPos.x() - offsetX(), globalPos.y() - offsetY() ); + Q_ASSERT( string->at( idx )->isCustom() ); + ox = string->at( idx )->x; + string->at( idx )->customItem()->enterAt( this, doc, string, idx, ox, oy, p ); +} + +void QTextCursor::invalidateNested() +{ + if ( nested ) { + QValueStack<QTextParag*>::Iterator it = parags.begin(); + QValueStack<int>::Iterator it2 = indices.begin(); + for ( ; it != parags.end(); ++it, ++it2 ) { + if ( *it == string ) + continue; + (*it)->invalidate( 0 ); + if ( (*it)->at( *it2 )->isCustom() ) + (*it)->at( *it2 )->customItem()->invalidate(); + } + } +} + +void QTextCursor::insert( const QString &str, bool checkNewLine, QMemArray<QTextStringChar> *formatting ) +{ + tmpIndex = -1; + bool justInsert = TRUE; + QString s( str ); +#if defined(Q_WS_WIN) + if ( checkNewLine ) + s = s.replace( QRegExp( "\\r" ), "" ); +#endif + if ( checkNewLine ) + justInsert = s.find( '\n' ) == -1; + if ( justInsert ) { + string->insert( idx, s ); + if ( formatting ) { + for ( int i = 0; i < (int)s.length(); ++i ) { + if ( formatting->at( i ).format() ) { + formatting->at( i ).format()->addRef(); + string->string()->setFormat( idx + i, formatting->at( i ).format(), TRUE ); + } + } + } + idx += s.length(); + } else { + QStringList lst = QStringList::split( '\n', s, TRUE ); + QStringList::Iterator it = lst.begin(); + int y = string->rect().y() + string->rect().height(); + int lastIndex = 0; + QTextFormat *lastFormat = 0; + for ( ; it != lst.end(); ) { + if ( it != lst.begin() ) { + splitAndInsertEmptyParag( FALSE, TRUE ); + string->setEndState( -1 ); + string->prev()->format( -1, FALSE ); + if ( lastFormat && formatting && string->prev() ) { + lastFormat->addRef(); + string->prev()->string()->setFormat( string->prev()->length() - 1, lastFormat, TRUE ); + } + } + lastFormat = 0; + QString s = *it; + ++it; + if ( !s.isEmpty() ) + string->insert( idx, s ); + else + string->invalidate( 0 ); + if ( formatting ) { + int len = s.length(); + for ( int i = 0; i < len; ++i ) { + if ( formatting->at( i + lastIndex ).format() ) { + formatting->at( i + lastIndex ).format()->addRef(); + string->string()->setFormat( i + idx, formatting->at( i + lastIndex ).format(), TRUE ); + } + } + if ( it != lst.end() ) + lastFormat = formatting->at( len + lastIndex ).format(); + ++len; + lastIndex += len; + } + + idx += s.length(); + } + string->format( -1, FALSE ); + int dy = string->rect().y() + string->rect().height() - y; + QTextParag *p = string; + p->setParagId( p->prev()->paragId() + 1 ); + 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 = string->rect().height(); + string->format( -1, TRUE ); + if ( h != string->rect().height() ) + invalidateNested(); + else if ( doc && doc->parent() ) + doc->nextDoubleBuffered = TRUE; +} + +void QTextCursor::gotoLeft() +{ + if ( string->string()->isRightToLeft() ) + gotoNextLetter(); + else + gotoPreviousLetter(); +} + +void QTextCursor::gotoPreviousLetter() +{ + tmpIndex = -1; + + if ( idx > 0 ) { + idx--; + } else if ( string->prev() ) { + QTextParag *s = string->prev(); + while ( s && !s->isVisible() ) + s = s->prev(); + if ( s ) { + string = s; + idx = string->length() - 1; + } + } else { + if ( nested ) { + pop(); + processNesting( Prev ); + if ( idx == -1 ) { + pop(); + if ( idx > 0 ) { + idx--; + } else if ( string->prev() ) { + string = string->prev(); + idx = string->length() - 1; + } + } + } + } + + const QTextStringChar *tsc = string->at( idx ); + if ( tsc && tsc->isCustom() && tsc->customItem()->isNested() ) { + processNesting( EnterEnd ); + } +} + +void QTextCursor::push() +{ + indices.push( idx ); + parags.push( string ); + xOffsets.push( ox ); + yOffsets.push( oy ); + nestedStack.push( nested ); +} + +void QTextCursor::pop() +{ + if ( !doc ) + return; + idx = indices.pop(); + string = parags.pop(); + ox = xOffsets.pop(); + oy = yOffsets.pop(); + if ( doc->parent() ) + doc = doc->parent(); + nested = nestedStack.pop(); +} + +void QTextCursor::restoreState() +{ + while ( !indices.isEmpty() ) + pop(); +} + +bool QTextCursor::place( const QPoint &p, QTextParag *s, bool link ) +{ + QPoint pos( p ); + QRect r; + QTextParag *str = s; + if ( pos.y() < s->rect().y() ) + pos.setY( s->rect().y() ); + while ( s ) { + r = s->rect(); + r.setWidth( doc ? doc->width() : QWIDGETSIZE_MAX ); + if ( s->isVisible() ) + str = s; + if ( pos.y() >= r.y() && pos.y() <= r.y() + r.height() || !s->next() ) + break; + s = s->next(); + } + + if ( !s || !str ) + return FALSE; + + s = str; + + setParag( s, FALSE ); + int y = s->rect().y(); + int lines = s->lines(); + QTextStringChar *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 && 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 ); + 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 { + if( chr->rightToLeft ) + cpos += cw; + int d = cpos - pos.x(); + bool dm = d < 0 ? !chr->rightToLeft : chr->rightToLeft; + if ( QABS( d ) < dist || (dist == d && dm == TRUE ) ) { + dist = QABS( d ); + if ( !link || pos.x() >= x + chr->x ) + curpos = i; + } + } + i++; + } + setIndex( curpos, FALSE ); + + if ( inCustom && doc && parag()->at( curpos )->isCustom() && parag()->at( curpos )->customItem()->isNested() ) { + QTextDocument *oldDoc = doc; + gotoIntoNested( pos ); + if ( oldDoc == doc ) + return TRUE; + QPoint p( pos.x() - offsetX(), pos.y() - offsetY() ); + if ( !place( p, document()->firstParag(), link ) ) + pop(); + } + return TRUE; +} + +void QTextCursor::processNesting( Operation op ) +{ + if ( !doc ) + return; + push(); + ox = string->at( idx )->x; + int bl, y; + string->lineHeightOfChar( idx, &bl, &y ); + oy = y + string->rect().y(); + nested = TRUE; + bool ok = FALSE; + + switch ( op ) { + case EnterBegin: + ok = string->at( idx )->customItem()->enter( this, doc, string, idx, ox, oy ); + break; + case EnterEnd: + ok = string->at( idx )->customItem()->enter( this, doc, string, idx, ox, oy, TRUE ); + break; + case Next: + ok = string->at( idx )->customItem()->next( this, doc, string, idx, ox, oy ); + break; + case Prev: + ok = string->at( idx )->customItem()->prev( this, doc, string, idx, ox, oy ); + break; + case Down: + ok = string->at( idx )->customItem()->down( this, doc, string, idx, ox, oy ); + break; + case Up: + ok = string->at( idx )->customItem()->up( this, doc, string, idx, ox, oy ); + break; + } + if ( !ok ) + pop(); +} + +void QTextCursor::gotoRight() +{ + if ( string->string()->isRightToLeft() ) + gotoPreviousLetter(); + else + gotoNextLetter(); +} + +void QTextCursor::gotoNextLetter() +{ + tmpIndex = -1; + + const QTextStringChar *tsc = string->at( idx ); + if ( tsc && tsc->isCustom() && tsc->customItem()->isNested() ) { + processNesting( EnterBegin ); + return; + } + + if ( idx < string->length() - 1 ) { + idx++; + } else if ( string->next() ) { + QTextParag *s = string->next(); + while ( s && !s->isVisible() ) + s = s->next(); + if ( s ) { + string = s; + idx = 0; + } + } else { + if ( nested ) { + pop(); + processNesting( Next ); + if ( idx == -1 ) { + pop(); + if ( idx < string->length() - 1 ) { + idx++; + } else if ( string->next() ) { + string = string->next(); + idx = 0; + } + } + } + } +} + +void QTextCursor::gotoUp() +{ + int indexOfLineStart; + int line; + QTextStringChar *c = string->lineStartOfChar( idx, &indexOfLineStart, &line ); + if ( !c ) + return; + + tmpIndex = QMAX( tmpIndex, idx - indexOfLineStart ); + if ( indexOfLineStart == 0 ) { + if ( !string->prev() ) { + if ( !nested ) + return; + pop(); + processNesting( Up ); + if ( idx == -1 ) { + pop(); + if ( !string->prev() ) + return; + idx = tmpIndex = 0; + } else { + tmpIndex = -1; + return; + } + } + QTextParag *s = string->prev(); + while ( s && !s->isVisible() ) + s = s->prev(); + if ( s ) + string = s; + int lastLine = string->lines() - 1; + if ( !string->lineStartOfLine( lastLine, &indexOfLineStart ) ) + return; + if ( indexOfLineStart + tmpIndex < string->length() ) + idx = indexOfLineStart + tmpIndex; + else + idx = string->length() - 1; + } else { + --line; + int oldIndexOfLineStart = indexOfLineStart; + if ( !string->lineStartOfLine( line, &indexOfLineStart ) ) + return; + if ( indexOfLineStart + tmpIndex < oldIndexOfLineStart ) + idx = indexOfLineStart + tmpIndex; + else + idx = oldIndexOfLineStart - 1; + } +} + +void QTextCursor::gotoDown() +{ + int indexOfLineStart; + int line; + QTextStringChar *c = string->lineStartOfChar( idx, &indexOfLineStart, &line ); + if ( !c ) + return; + + tmpIndex = QMAX( tmpIndex, idx - indexOfLineStart ); + if ( line == string->lines() - 1 ) { + if ( !string->next() ) { + if ( !nested ) + return; + pop(); + processNesting( Down ); + if ( idx == -1 ) { + pop(); + if ( !string->next() ) + return; + idx = tmpIndex = 0; + } else { + tmpIndex = -1; + return; + } + } + QTextParag *s = string->next(); + while ( s && !s->isVisible() ) + s = s->next(); + if ( s ) + string = s; + if ( !string->lineStartOfLine( 0, &indexOfLineStart ) ) + return; + int end; + if ( string->lines() == 1 ) + end = string->length(); + else + string->lineStartOfLine( 1, &end ); + if ( indexOfLineStart + tmpIndex < end ) + idx = indexOfLineStart + tmpIndex; + else + idx = end - 1; + } else { + ++line; + int end; + if ( line == string->lines() - 1 ) + end = string->length(); + else + string->lineStartOfLine( line + 1, &end ); + if ( !string->lineStartOfLine( line, &indexOfLineStart ) ) + return; + if ( indexOfLineStart + tmpIndex < end ) + idx = indexOfLineStart + tmpIndex; + else + idx = end - 1; + } +} + +void QTextCursor::gotoLineEnd() +{ + tmpIndex = -1; + int indexOfLineStart; + int line; + QTextStringChar *c = string->lineStartOfChar( idx, &indexOfLineStart, &line ); + if ( !c ) + return; + + if ( line == string->lines() - 1 ) { + idx = string->length() - 1; + } else { + c = string->lineStartOfLine( ++line, &indexOfLineStart ); + indexOfLineStart--; + idx = indexOfLineStart; + } +} + +void QTextCursor::gotoLineStart() +{ + tmpIndex = -1; + int indexOfLineStart; + int line; + QTextStringChar *c = string->lineStartOfChar( idx, &indexOfLineStart, &line ); + if ( !c ) + return; + + idx = indexOfLineStart; +} + +void QTextCursor::gotoHome() +{ + tmpIndex = -1; + if ( doc ) + string = doc->firstParag(); + idx = 0; +} + +void QTextCursor::gotoEnd() +{ + if ( doc && !doc->lastParag()->isValid() ) + return; + + tmpIndex = -1; + if ( doc ) + string = doc->lastParag(); + idx = string->length() - 1; +} + +void QTextCursor::gotoPageUp( int visibleHeight ) +{ + tmpIndex = -1; + QTextParag *s = string; + int h = visibleHeight; + int y = s->rect().y(); + while ( s ) { + if ( y - s->rect().y() >= h ) + break; + s = s->prev(); + } + + if ( !s && doc ) + s = doc->firstParag(); + + string = s; + idx = 0; +} + +void QTextCursor::gotoPageDown( int visibleHeight ) +{ + tmpIndex = -1; + QTextParag *s = string; + int h = visibleHeight; + int y = s->rect().y(); + while ( s ) { + if ( s->rect().y() - y >= h ) + break; + s = s->next(); + } + + if ( !s && doc ) { + s = doc->lastParag(); + string = s; + idx = string->length() - 1; + return; + } + + if ( !s->isValid() ) + return; + + string = s; + idx = 0; +} + +void QTextCursor::gotoWordRight() +{ + if ( string->string()->isRightToLeft() ) + gotoPreviousWord(); + else + gotoNextWord(); +} + +void QTextCursor::gotoWordLeft() +{ + if ( string->string()->isRightToLeft() ) + gotoNextWord(); + else + gotoPreviousWord(); +} + +void QTextCursor::gotoPreviousWord() +{ + gotoPreviousLetter(); + tmpIndex = -1; + QTextString *s = string->string(); + bool allowSame = FALSE; + if ( idx == ((int)s->length()-1) ) + return; + for ( int i = idx; i >= 0; --i ) { + if ( s->at( i ).c.isSpace() || s->at( i ).c == '\t' || s->at( i ).c == '.' || + s->at( i ).c == ',' || s->at( i ).c == ':' || s->at( i ).c == ';' ) { + if ( !allowSame ) + continue; + idx = i + 1; + return; + } + if ( !allowSame && !( s->at( i ).c.isSpace() || s->at( i ).c == '\t' || s->at( i ).c == '.' || + s->at( i ).c == ',' || s->at( i ).c == ':' || s->at( i ).c == ';' ) ) + allowSame = TRUE; + } + idx = 0; +} + +void QTextCursor::gotoNextWord() +{ + tmpIndex = -1; + QTextString *s = string->string(); + bool allowSame = FALSE; + for ( int i = idx; i < (int)s->length(); ++i ) { + if ( ! (s->at( i ).c.isSpace() || s->at( i ).c == '\t' || s->at( i ).c == '.' || + s->at( i ).c == ',' || s->at( i ).c == ':' || s->at( i ).c == ';') ) { + if ( !allowSame ) + continue; + idx = i; + return; + } + if ( !allowSame && ( s->at( i ).c.isSpace() || s->at( i ).c == '\t' || s->at( i ).c == '.' || + s->at( i ).c == ',' || s->at( i ).c == ':' || s->at( i ).c == ';' ) ) + allowSame = TRUE; + + } + + if ( idx < ((int)s->length()-1) ) { + gotoLineEnd(); + } else if ( string->next() ) { + QTextParag *s = string->next(); + while ( s && !s->isVisible() ) + s = s->next(); + if ( s ) { + string = s; + idx = 0; + } + } else { + gotoLineEnd(); + } +} + +bool QTextCursor::atParagStart() +{ + return idx == 0; +} + +bool QTextCursor::atParagEnd() +{ + return idx == string->length() - 1; +} + +void QTextCursor::splitAndInsertEmptyParag( bool ind, bool updateIds ) +{ + if ( !doc ) + return; + tmpIndex = -1; + QTextFormat *f = 0; + if ( doc->useFormatCollection() ) { + f = string->at( idx )->format(); + if ( idx == string->length() - 1 && idx > 0 ) + f = string->at( idx - 1 )->format(); + if ( f->isMisspelled() ) { + f->removeRef(); + f = doc->formatCollection()->format( f->font(), f->color() ); + } + } + + if ( atParagEnd() ) { + QTextParag *n = string->next(); + QTextParag *s = doc->createParag( doc, string, n, updateIds ); + if ( f ) + s->setFormat( 0, 1, f, TRUE ); + s->copyParagData( string ); + if ( ind ) { + int oi, ni; + s->indent( &oi, &ni ); + string = s; + idx = ni; + } else { + string = s; + idx = 0; + } + } else if ( atParagStart() ) { + QTextParag *p = string->prev(); + QTextParag *s = doc->createParag( doc, p, string, updateIds ); + if ( f ) + s->setFormat( 0, 1, f, TRUE ); + s->copyParagData( string ); + if ( ind ) { + s->indent(); + s->format(); + indent(); + string->format(); + } + } else { + QString str = string->string()->toString().mid( idx, 0xFFFFFF ); + QTextParag *n = string->next(); + QTextParag *s = doc->createParag( doc, string, n, updateIds ); + s->copyParagData( string ); + s->remove( 0, 1 ); + s->append( str, TRUE ); + for ( uint i = 0; i < str.length(); ++i ) { + s->setFormat( i, 1, string->at( idx + i )->format(), TRUE ); + if ( string->at( idx + i )->isCustom() ) { + QTextCustomItem * item = string->at( idx + i )->customItem(); + s->at( i )->setCustomItem( item ); + string->at( idx + i )->loseCustomItem(); + } + } + string->truncate( idx ); + if ( ind ) { + int oi, ni; + s->indent( &oi, &ni ); + string = s; + idx = ni; + } else { + string = s; + idx = 0; + } + } + + invalidateNested(); +} + +bool QTextCursor::remove() +{ + tmpIndex = -1; + if ( !atParagEnd() ) { + string->remove( idx, 1 ); + int h = string->rect().height(); + string->format( -1, TRUE ); + if ( h != string->rect().height() ) + invalidateNested(); + else if ( doc && doc->parent() ) + doc->nextDoubleBuffered = TRUE; + return FALSE; + } else if ( string->next() ) { + if ( string->length() == 1 ) { + string->next()->setPrev( string->prev() ); + if ( string->prev() ) + string->prev()->setNext( string->next() ); + QTextParag *p = string->next(); + delete string; + string = p; + string->invalidate( 0 ); + QTextParag *s = string; + while ( s ) { + s->id = s->p ? s->p->id + 1 : 0; + s->state = -1; + s->needPreProcess = TRUE; + s->changed = TRUE; + s = s->n; + } + string->format(); + } else { + string->join( string->next() ); + } + invalidateNested(); + return TRUE; + } + return FALSE; +} + +void QTextCursor::killLine() +{ + if ( atParagEnd() ) + return; + string->remove( idx, string->length() - idx - 1 ); + int h = string->rect().height(); + string->format( -1, TRUE ); + if ( h != string->rect().height() ) + invalidateNested(); + else if ( doc && doc->parent() ) + doc->nextDoubleBuffered = TRUE; +} + +void QTextCursor::indent() +{ + int oi = 0, ni = 0; + string->indent( &oi, &ni ); + if ( oi == ni ) + return; + + if ( idx >= oi ) + idx += ni - oi; + else + idx = ni; +} + +void QTextCursor::setDocument( QTextDocument *d ) +{ + doc = d; + string = d->firstParag(); + idx = 0; + nested = FALSE; + restoreState(); + tmpIndex = -1; +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextDocument::QTextDocument( QTextDocument *p ) + : par( p ), parParag( 0 ), tc( 0 ), tArray( 0 ), tStopWidth( 0 ) +{ + fCollection = new QTextFormatCollection; + init(); +} + +QTextDocument::QTextDocument( QTextDocument *p, QTextFormatCollection *f ) + : par( p ), parParag( 0 ), tc( 0 ), tArray( 0 ), tStopWidth( 0 ) +{ + fCollection = f; + init(); +} + +void QTextDocument::init() +{ +#if defined(PARSER_DEBUG) + qDebug( debug_indent + "new QTextDocument (%p)", this ); +#endif + 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 = AlignAuto; + nSelections = 1; + addMargs = FALSE; + + sheet_ = QStyleSheet::defaultSheet(); + factory_ = QMimeSourceFactory::defaultFactory(); + contxt = QString::null; + fCollection->setStyleSheet( sheet_ ); + + underlLinks = par ? par->underlLinks : TRUE; + backBrush = 0; + buf_pixmap = 0; + nextDoubleBuffered = FALSE; + + if ( par ) + withoutDoubleBuffer = par->withoutDoubleBuffer; + else + withoutDoubleBuffer = FALSE; + + lParag = fParag = createParag( this, 0, 0 ); + tmpCursor = 0; + + cx = 0; + cy = 2; + if ( par ) + cx = cy = 0; + cw = 600; + vw = 0; + flow_ = new QTextFlow; + flow_->setWidth( cw ); + + leftmargin = rightmargin = 4; + + selectionColors[ Standard ] = QApplication::palette().color( QPalette::Active, QColorGroup::Highlight ); + selectionText[ Standard ] = TRUE; + commandHistory = new QTextCommandHistory( 100 ); + tStopWidth = formatCollection()->defaultFormat()->width( 'x' ) * 8; +} + +QTextDocument::~QTextDocument() +{ + if ( par ) + par->removeChild( this ); + clear(); + delete commandHistory; + delete flow_; + if ( !par ) + delete pFormatter; + delete fCollection; + delete pProcessor; + delete buf_pixmap; + delete indenter; + delete backBrush; + if ( tArray ) + delete [] tArray; +} + +void QTextDocument::clear( bool createEmptyParag ) +{ + if ( flow_ ) + flow_->clear(); + while ( fParag ) { + QTextParag *p = fParag->next(); + delete fParag; + fParag = p; + } + fParag = lParag = 0; + if ( createEmptyParag ) + fParag = lParag = createParag( this ); + selections.clear(); +} + +int QTextDocument::widthUsed() const +{ + return wused + border_tolerance; +} + +int QTextDocument::height() const +{ + int h = 0; + if ( lParag ) + h = lParag->rect().top() + lParag->rect().height() + 1; + int fh = flow_->boundingRect().bottom(); + return QMAX( h, fh ); +} + + + +QTextParag *QTextDocument::createParag( QTextDocument *d, QTextParag *pr, QTextParag *nx, bool updateIds ) +{ + return new QTextParag( d, pr, nx, updateIds ); +} + +bool QTextDocument::setMinimumWidth( int needed, int used, QTextParag *p ) +{ + if ( needed == -1 ) { + minw = 0; + wused = 0; + p = 0; + } + if ( p == minwParag ) { + 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 QTextDocument::setPlainText( const QString &text ) +{ + clear(); + preferRichText = FALSE; + oTextValid = TRUE; + oText = text; + + int lastNl = 0; + int nl = text.find( '\n' ); + if ( nl == -1 ) { + lParag = createParag( this, lParag, 0 ); + if ( !fParag ) + fParag = lParag; + QString s = text; + if ( !s.isEmpty() ) { + if ( s[ (int)s.length() - 1 ] == '\r' ) + s.remove( s.length() - 1, 1 ); + lParag->append( s ); + } + } else { + for (;;) { + lParag = createParag( this, lParag, 0 ); + if ( !fParag ) + fParag = lParag; + QString s = text.mid( lastNl, nl - lastNl ); + if ( !s.isEmpty() ) { + if ( s[ (int)s.length() - 1 ] == '\r' ) + s.remove( s.length() - 1, 1 ); + lParag->append( s ); + } + if ( nl == 0xffffff ) + break; + lastNl = nl + 1; + nl = text.find( '\n', nl + 1 ); + if ( nl == -1 ) + nl = 0xffffff; + } + } + if ( !lParag ) + lParag = fParag = createParag( this, 0, 0 ); +} + +struct Q_EXPORT QTextDocumentTag { + QTextDocumentTag(){} + QTextDocumentTag( const QString&n, const QStyleSheetItem* s, const QTextFormat& f ) + :name(n),style(s), format(f), alignment(Qt3::AlignAuto), direction(QChar::DirON),liststyle(QStyleSheetItem::ListDisc) { + wsm = QStyleSheetItem::WhiteSpaceNormal; + } + QString name; + const QStyleSheetItem* style; + QString anchorHref; + QStyleSheetItem::WhiteSpaceMode wsm; + QTextFormat format; + int alignment : 16; + int direction : 5; + QStyleSheetItem::ListStyle liststyle; + + QTextDocumentTag( const QTextDocumentTag& 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; + } + QTextDocumentTag& operator=(const QTextDocumentTag& 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; + } + +#if defined(Q_FULL_TEMPLATE_INSTANTIATION) + bool operator==( const QTextDocumentTag& ) const { return FALSE; } +#endif +}; + +#define NEWPAR do{ if ( !hasNewPar ) curpar = createParag( this, curpar ); \ + if ( curpar->isBr ) curpar->isBr = FALSE; \ + hasNewPar = TRUE; \ + curpar->setAlignment( curtag.alignment ); \ + curpar->setDirection( (QChar::Direction)curtag.direction ); \ + space = TRUE; \ + QPtrVector<QStyleSheetItem> vec( (uint)tags.count() + 1); \ + int i = 0; \ + for ( QValueStack<QTextDocumentTag>::Iterator it = tags.begin(); it != tags.end(); ++it ) \ + vec.insert( i++, (*it).style ); \ + vec.insert( i, curtag.style ); \ + curpar->setStyleSheetItems( vec ); }while(FALSE) + + +void QTextDocument::setRichText( const QString &text, const QString &context ) +{ + setTextFormat( Qt::RichText ); + if ( !context.isEmpty() ) + setContext( context ); + clear(); + fParag = lParag = createParag( this ); + setRichTextInternal( text ); +} + +static QStyleSheetItem::ListStyle chooseListStyle( const QStyleSheetItem *nstyle, + const QMap<QString, QString> &attr, + QStyleSheetItem::ListStyle curListStyle ) +{ + if ( nstyle->name() == "ol" || nstyle->name() == "ul" ) { + curListStyle = nstyle->listStyle(); + QMap<QString, QString>::ConstIterator it = attr.find( "type" ); + if ( it != attr.end() ) { + QString sl = *it; + if ( sl == "1" ) { + curListStyle = QStyleSheetItem::ListDecimal; + } else if ( sl == "a" ) { + curListStyle = QStyleSheetItem::ListLowerAlpha; + } else if ( sl == "A" ) { + curListStyle = QStyleSheetItem::ListUpperAlpha; + } else { + sl = sl.lower(); + if ( sl == "square" ) + curListStyle = QStyleSheetItem::ListSquare; + else if ( sl == "disc" ) + curListStyle = QStyleSheetItem::ListDisc; + else if ( sl == "circle" ) + curListStyle = QStyleSheetItem::ListCircle; + } + } + } + return curListStyle; +} + +void QTextDocument::setRichTextInternal( const QString &text ) +{ + oTextValid = TRUE; + oText = text; + QTextParag* curpar = lParag; + int pos = 0; + QValueStack<QTextDocumentTag> tags; + QTextDocumentTag initag( "", sheet_->item(""), *formatCollection()->defaultFormat() ); + QTextDocumentTag curtag = initag; + bool space = TRUE; + + const QChar* doc = text.unicode(); + int length = text.length(); + bool hasNewPar = curpar->length() <= 1; + QString lastClose; + QString anchorName; + while ( pos < length ) { + if ( hasPrefix(doc, length, pos, '<' ) ){ + if ( !hasPrefix( doc, length, pos+1, QChar('/') ) ) { + // open tag + QMap<QString, QString> attr; + bool emptyTag = FALSE; + QString tagname = parseOpenTag(doc, length, pos, attr, emptyTag); + if ( tagname.isEmpty() ) + continue; // nothing we could do with this, probably parse error + + if ( tagname == "title" ) { + QString title; + while ( pos < length ) { + if ( hasPrefix( doc, length, pos, QChar('<') ) && hasPrefix( doc, length, pos+1, QChar('/') ) && + parseCloseTag( doc, length, pos ) == "title" ) + break; + title += doc[ pos ]; + ++pos; + } + attribs.replace( "title", title ); + } + + const QStyleSheetItem* nstyle = sheet_->item(tagname); + + if ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ) { +// if ( tagname == "br" ) { +// // our standard br emty-tag handling breaks +// // inside list items, we would get another +// // list item in this case. As workaround, fake +// // a new paragraph instead +// tagname = "p"; +// nstyle = sheet_->item( tagname ); +// } + if ( nstyle ) + hasNewPar = FALSE; // we want empty paragraphs in this case + } + + 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. We do not want to nest there for HTML compatibility + if ( nstyle->displayMode() == QStyleSheetItem::DisplayBlock ) { + while ( curtag.style->name() == "p" ) { + if ( tags.isEmpty() ) + break; + curtag = tags.pop(); + } + } + + } + + QTextCustomItem* custom = 0; + // some well-known empty tags + if ( tagname == "br" ) { + emptyTag = TRUE; + hasNewPar = FALSE; + if ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ) { + // when linebreaking a list item, we do not + // actually want a new list item but just a + // new line. Fake this by pushing a paragraph + // onto the stack + tags.push( curtag ); + curtag.name = tagname; + curtag.style = nstyle; + } + NEWPAR; + curpar->isBr = TRUE; + curpar->setAlignment( curtag.alignment ); + } else if ( tagname == "hr" ) { + emptyTag = TRUE; + custom = sheet_->tag( tagname, attr, contxt, *factory_ , emptyTag, this ); + NEWPAR; + } else if ( tagname == "table" ) { + QTextFormat format = curtag.format.makeTextFormat( nstyle, attr ); + curpar->setAlignment( curtag.alignment ); + custom = parseTable( attr, format, doc, length, pos, curpar ); + (void)eatSpace( doc, length, pos ); + emptyTag = TRUE; + } else if ( tagname == "qt" ) { + for ( QMap<QString, QString>::Iterator it = attr.begin(); it != attr.end(); ++it ) { + if ( it.key() == "bgcolor" ) { + QBrush *b = new QBrush( QColor( *it ) ); + setPaper( b ); + } else if ( it.key() == "background" ) { + QImage img; + const QMimeSource* m = factory_->data( *it, contxt ); + if ( !m ) { + qWarning("QRichText: no mimesource for %s", (*it).latin1() ); + } else { + if ( !QImageDrag::decode( m, img ) ) { + qWarning("QTextImage: cannot decode %s", (*it).latin1() ); + } + } + if ( !img.isNull() ) { + QPixmap pm; + pm.convertFromImage( img ); + QBrush *b = new QBrush( QColor(), pm ); + setPaper( b ); + } + } else if ( it.key() == "text" ) { + QColor c( *it ); + if ( formatCollection()->defaultFormat()->color() != c ) { + QDict<QTextFormat> formats = formatCollection()->dict(); + QDictIterator<QTextFormat> it( formats ); + while ( it.current() ) { + if ( it.current() == formatCollection()->defaultFormat() ) { + ++it; + continue; + } + it.current()->setColor( c ); + ++it; + } + formatCollection()->defaultFormat()->setColor( c ); + curtag.format.setColor( c ); + } + } else if ( it.key() == "link" ) { + linkColor = QColor( *it ); + } else if ( it.key() == "title" ) { + attribs.replace( it.key(), *it ); + } + } + } else { + custom = sheet_->tag( tagname, attr, contxt, *factory_ , emptyTag, this ); + } + + if ( !nstyle && !custom ) // we have no clue what this tag could be, ignore it + continue; + + if ( custom ) { + int index = curpar->length() - 1; + if ( index < 0 ) + index = 0; + QTextFormat format = curtag.format.makeTextFormat( nstyle, attr ); + curpar->append( QChar('*') ); + curpar->setFormat( index, 1, &format ); + curpar->at( index )->setCustomItem( custom ); + if ( !curtag.anchorHref.isEmpty() ) + curpar->at(index)->setAnchor( QString::null, curtag.anchorHref ); + if ( !anchorName.isEmpty() ) { + curpar->at(index)->setAnchor( anchorName, curpar->at(index)->anchorHref() ); + anchorName = QString::null; + } + registerCustomItem( custom, curpar ); + hasNewPar = FALSE; + } else if ( !emptyTag ) { + // ignore whitespace for inline elements if there was already one + if ( nstyle->whiteSpaceMode() == QStyleSheetItem::WhiteSpaceNormal + && ( space || nstyle->displayMode() != QStyleSheetItem::DisplayInline ) ) + eatSpace( doc, length, pos ); + + // if we do nesting, push curtag on the stack, + // otherwise reinint curag. + if ( nstyle != curtag.style || nstyle->selfNesting() ) { + tags.push( curtag ); + } else { + if ( !tags.isEmpty() ) + curtag = tags.top(); + else + curtag = initag; + } + + const QStyleSheetItem* ostyle = curtag.style; + + curtag.name = tagname; + curtag.style = nstyle; + curtag.name = tagname; + curtag.style = nstyle; + if ( nstyle->whiteSpaceMode() != QStyleSheetItem::WhiteSpaceNormal ) + curtag.wsm = nstyle->whiteSpaceMode(); + curtag.liststyle = chooseListStyle( nstyle, attr, curtag.liststyle ); + curtag.format = curtag.format.makeTextFormat( nstyle, attr ); + if ( nstyle->isAnchor() ) { + if ( !anchorName.isEmpty() ) + anchorName += "#" + attr["name"]; + else + anchorName = attr["name"]; + curtag.anchorHref = attr["href"]; + } + + if ( nstyle->alignment() != QStyleSheetItem::Undefined ) + curtag.alignment = nstyle->alignment(); + + if ( ostyle->displayMode() == QStyleSheetItem::DisplayListItem && + curpar->length() <= 1 + && nstyle->displayMode() == QStyleSheetItem::DisplayBlock ) { + // do not do anything, we reuse the paragraph we have + } else if ( nstyle->displayMode() != QStyleSheetItem::DisplayInline && nstyle->displayMode() != QStyleSheetItem::DisplayNone ) { + NEWPAR; + } + + if ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ) { + curpar->setListStyle( curtag.liststyle ); + if ( attr.find( "value" ) != attr.end() ) + curpar->setListValue( (*attr.find( "value" )).toInt() ); + } + + if ( nstyle->displayMode() != QStyleSheetItem::DisplayInline ) + curpar->setFormat( &curtag.format ); + + if ( attr.contains( "align" ) && + ( curtag.name == "p" || + curtag.name == "div" || + curtag.name == "li" || + curtag.name[ 0 ] == 'h' ) ) { + QString align = attr["align"]; + if ( align == "center" ) + curtag.alignment = Qt::AlignCenter; + else if ( align == "right" ) + curtag.alignment = Qt::AlignRight; + else if ( align == "justify" ) + curtag.alignment = Qt3::AlignJustify; + } + if ( attr.contains( "dir" ) && + ( curtag.name == "p" || + curtag.name == "div" || + curtag.name == "li" || + curtag.name[ 0 ] == 'h' ) ) { + QString dir = attr["dir"]; + if ( dir == "rtl" ) + curtag.direction = QChar::DirR; + else if ( dir == "ltr" ) + curtag.direction = QChar::DirL; + } + if ( nstyle->displayMode() != QStyleSheetItem::DisplayInline ) { + curpar->setAlignment( curtag.alignment ); + curpar->setDirection( (QChar::Direction)curtag.direction ); + } + } + } else { + QString tagname = parseCloseTag( doc, length, pos ); + lastClose = tagname; + if ( tagname.isEmpty() ) + continue; // nothing we could do with this, probably parse error + if ( !sheet_->item( tagname ) ) // ignore unknown tags + continue; + + + // we close a block item. Since the text may continue, we need to have a new paragraph + bool needNewPar = curtag.style->displayMode() == QStyleSheetItem::DisplayBlock; + + if ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ) { + needNewPar = TRUE; + hasNewPar = FALSE; // we want empty paragraphs in this case + } + + // 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 ( curtag.style->displayMode() == QStyleSheetItem::DisplayListItem ) { + tags.push( curtag ); + curtag.name = "p"; + curtag.style = sheet_->item( curtag.name ); // a list item continues, use p for that + } + NEWPAR; + } + } + } else { + // normal contents + QString s; + QChar c; + while ( pos < length && !hasPrefix(doc, length, pos, QChar('<') ) ){ + QStyleSheetItem::WhiteSpaceMode wsm = curtag.wsm; + if ( s.length() > 4096 ) + wsm = (QStyleSheetItem::WhiteSpaceMode)QStyleSheetItem_WhiteSpaceNormalWithNewlines; + + c = parseChar( doc, length, pos, wsm ); + + if ( c == '\n' ) // happens only in whitespacepre-mode or with WhiteSpaceNormalWithNewlines. + break; // we want a new line in this case + + bool c_isSpace = c.isSpace() && c.unicode() != 0x00a0U && + curtag.wsm != QStyleSheetItem_WhiteSpaceNoCompression; + + if ( curtag.wsm == QStyleSheetItem::WhiteSpaceNormal && c_isSpace && space ) + continue; + if ( c == '\r' ) + continue; + space = c_isSpace; + s += c; + } + if ( !s.isEmpty() && curtag.style->displayMode() != QStyleSheetItem::DisplayNone ) { + hasNewPar = FALSE; + int index = curpar->length() - 1; + if ( index < 0 ) + index = 0; + curpar->append( s ); + QTextFormat* 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::null, curtag.anchorHref ); + } + if ( !anchorName.isEmpty() ) { + curpar->at(index)->setAnchor( anchorName, curpar->at(index)->anchorHref() ); + anchorName = QString::null; + } + } + if ( c == '\n' ) { // happens in WhiteSpacePre mode + hasNewPar = FALSE; + tags.push( curtag ); + NEWPAR; + curtag = tags.pop(); + } + } + } + + if ( hasNewPar && curpar != fParag ) { + // 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 = QString::null; + } +} + +void QTextDocument::setText( const QString &text, const QString &context ) +{ + focusIndicator.parag = 0; + selections.clear(); + if ( txtFormat == Qt::AutoText && QStyleSheet::mightBeRichText( text ) || + txtFormat == Qt::RichText ) + setRichText( text, context ); + else + setPlainText( text ); +} + +QString QTextDocument::plainText( QTextParag *p ) const +{ + if ( !p ) { + QString buffer; + QString s; + QTextParag *p = fParag; + while ( p ) { + if ( !p->mightHaveCustomItems ) { + s = p->string()->toString(); + } else { + for ( int i = 0; i < p->length() - 1; ++i ) { + if ( p->at( i )->isCustom() ) { + if ( p->at( i )->customItem()->isNested() ) { + s += "\n"; + QTextTable *t = (QTextTable*)p->at( i )->customItem(); + QPtrList<QTextTableCell> cells = t->tableCells(); + for ( QTextTableCell *c = cells.first(); c; c = cells.next() ) + s += c->richText()->plainText() + "\n"; + s += "\n"; + } + } else { + s += p->at( i )->c; + } + } + } + s.remove( s.length() - 1, 1 ); + if ( p->next() ) + s += "\n"; + buffer += s; + p = p->next(); + } + return buffer; + } else { + return p->string()->toString(); + } +} + +static QString align_to_string( const QString &tag, int a ) +{ + if ( tag == "p" || tag == "li" || ( tag[0] == 'h' && tag[1].isDigit() ) ) { + if ( a & Qt::AlignRight ) + return " align=\"right\""; + if ( a & Qt::AlignCenter ) + return " align=\"center\""; + if ( a & Qt3::AlignJustify ) + return " align=\"justify\""; + } + return ""; +} + +static QString direction_to_string( const QString &tag, int d ) +{ + if ( d != QChar::DirON && + ( tag == "p" || tag == "div" || tag == "li" || ( tag[0] == 'h' && tag[1].isDigit() ) ) ) + return ( d == QChar::DirL? " dir=\"ltr\"" : " dir=\"rtl\"" ); + return ""; +} + +QString QTextDocument::richText( QTextParag *p ) const +{ + QString s,n; + if ( !p ) { + p = fParag; + QPtrVector<QStyleSheetItem> lastItems, items; + while ( p ) { + items = p->styleSheetItems(); + if ( items.size() ) { + QStyleSheetItem *item = items[ items.size() - 1 ]; + items.resize( items.size() - 1 ); + if ( items.size() > lastItems.size() ) { + for ( int i = lastItems.size(); i < (int)items.size(); ++i ) { + n = items[i]->name(); + if ( n.isEmpty() || n == "li" ) + continue; + s += "<" + n + align_to_string( n, p->alignment() ) + ">"; + } + } else { + QString end; + for ( int i = items.size(); i < (int)lastItems.size(); ++i ) { + n = lastItems[i]->name(); + if ( n.isEmpty() || n == "li" || n == "br" ) + continue; + end.prepend( "</" + lastItems[ i ]->name() + ">" ); + } + s += end; + } + lastItems = items; + n = item->name(); + if ( n == "li" && p->listValue() != -1 ) { + s += "<li value=\"" + QString::number( p->listValue() ) + "\">"; + } else { + QString ps = p->richText(); + if ( ps.isEmpty() ) + s += "<br>"; // empty paragraph + else if ( !n.isEmpty() ) { + s += "<" + n + align_to_string( n, p->alignment() ) + + direction_to_string( n, p->direction() ) + ">" + ps; + if ( n != "li" && n != "br") + s += "</" + n + ">"; + } else + s += ps; + } + } else { + QString end; + for ( int i = 0; i < (int)lastItems.size(); ++i ) { + QString n = lastItems[i]->name(); + if ( n.isEmpty() || n == "li" || n == "br" ) + continue; + end.prepend( "</" + n + ">" ); + } + s += end; + QString ps = p->richText(); + if ( ps.isEmpty() ) + s += "<br>"; // empty paragraph + else + s += "<p" + align_to_string( "p", p->alignment() ) + direction_to_string( "p", p->direction() ) + + ">" + ps + "</p>"; + lastItems = items; + } + if ( ( p = p->next() ) ) + s += '\n'; + } + } else { + s = p->richText(); + } + + return s; +} + +QString QTextDocument::text() const +{ + if ( plainText().simplifyWhiteSpace().isEmpty() ) + return QString(""); + if ( txtFormat == Qt::AutoText && preferRichText || txtFormat == Qt::RichText ) + return richText(); + return plainText( 0 ); +} + +QString QTextDocument::text( int parag ) const +{ + QTextParag *p = paragAt( parag ); + if ( !p ) + return QString::null; + + if ( txtFormat == Qt::AutoText && preferRichText || txtFormat == Qt::RichText ) + return richText( p ); + else + return plainText( p ); +} + +void QTextDocument::invalidate() +{ + QTextParag *s = fParag; + while ( s ) { + s->invalidate( 0 ); + s = s->next(); + } +} + +void QTextDocument::selectionStart( int id, int ¶gId, int &index ) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return; + QTextDocumentSelection &sel = *it; + paragId = !sel.swapped ? sel.startCursor.parag()->paragId() : sel.endCursor.parag()->paragId(); + index = !sel.swapped ? sel.startCursor.index() : sel.endCursor.index(); +} + +QTextCursor QTextDocument::selectionStartCursor( int id) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return QTextCursor( this ); + QTextDocumentSelection &sel = *it; + if ( sel.swapped ) + return sel.endCursor; + return sel.startCursor; +} + +QTextCursor QTextDocument::selectionEndCursor( int id) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return QTextCursor( this ); + QTextDocumentSelection &sel = *it; + if ( !sel.swapped ) + return sel.endCursor; + return sel.startCursor; +} + +void QTextDocument::selectionEnd( int id, int ¶gId, int &index ) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return; + QTextDocumentSelection &sel = *it; + paragId = sel.swapped ? sel.startCursor.parag()->paragId() : sel.endCursor.parag()->paragId(); + index = sel.swapped ? sel.startCursor.index() : sel.endCursor.index(); +} + +QTextParag *QTextDocument::selectionStart( int id ) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return 0; + QTextDocumentSelection &sel = *it; + if ( sel.startCursor.parag()->paragId() < sel.endCursor.parag()->paragId() ) + return sel.startCursor.parag(); + return sel.endCursor.parag(); +} + +QTextParag *QTextDocument::selectionEnd( int id ) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return 0; + QTextDocumentSelection &sel = *it; + if ( sel.startCursor.parag()->paragId() > sel.endCursor.parag()->paragId() ) + return sel.startCursor.parag(); + return sel.endCursor.parag(); +} + +void QTextDocument::addSelection( int id ) +{ + nSelections = QMAX( nSelections, id + 1 ); +} + +static void setSelectionEndHelper( int id, QTextDocumentSelection &sel, QTextCursor &start, QTextCursor &end ) +{ + QTextCursor c1 = start; + QTextCursor c2 = end; + if ( sel.swapped ) { + c1 = end; + c2 = start; + } + + c1.parag()->removeSelection( id ); + c2.parag()->removeSelection( id ); + if ( c1.parag() != c2.parag() ) { + c1.parag()->setSelection( id, c1.index(), c1.parag()->length() - 1 ); + c2.parag()->setSelection( id, 0, c2.index() ); + } else { + c1.parag()->setSelection( id, QMIN( c1.index(), c2.index() ), QMAX( c1.index(), c2.index() ) ); + } + + sel.startCursor = start; + sel.endCursor = end; + if ( sel.startCursor.parag() == sel.endCursor.parag() ) + sel.swapped = sel.startCursor.index() > sel.endCursor.index(); +} + +bool QTextDocument::setSelectionEnd( int id, QTextCursor *cursor ) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return FALSE; + QTextDocumentSelection &sel = *it; + + QTextCursor start = sel.startCursor; + QTextCursor end = *cursor; + + if ( start == end ) { + removeSelection( id ); + setSelectionStart( id, cursor ); + return TRUE; + } + + if ( sel.endCursor.parag() == end.parag() ) { + setSelectionEndHelper( id, sel, start, end ); + return TRUE; + } + + bool inSelection = FALSE; + QTextCursor c( this ); + QTextCursor tmp = sel.startCursor; + if ( sel.swapped ) + tmp = sel.endCursor; + tmp.restoreState(); + QTextCursor tmp2 = *cursor; + tmp2.restoreState(); + c.setParag( tmp.parag()->paragId() < tmp2.parag()->paragId() ? tmp.parag() : tmp2.parag() ); + QTextCursor old; + 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.parag() == start.parag() ) + hadStartParag = TRUE; + if ( c.parag() == end.parag() ) + hadEndParag = TRUE; + if ( c == sel.startCursor ) + hadOldStart = TRUE; + if ( c == sel.endCursor ) + hadOldEnd = TRUE; + + if ( !sel.swapped && + ( hadEnd && !hadStart || + hadEnd && hadStart && start.parag() == end.parag() && start.index() > end.index() ) ) + sel.swapped = TRUE; + + if ( c == end && hadStartParag || + c == start && hadEndParag ) { + QTextCursor tmp = c; + tmp.restoreState(); + if ( tmp.parag() != c.parag() ) { + int sstart = tmp.parag()->selectionStart( id ); + tmp.parag()->removeSelection( id ); + tmp.parag()->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.parag()->hasSelection( id ) && c.atParagEnd(); + c.parag()->removeSelection( id ); + if ( inSelection ) { + if ( c.parag() == start.parag() && start.parag() == end.parag() ) { + c.parag()->setSelection( id, QMIN( start.index(), end.index() ), QMAX( start.index(), end.index() ) ); + } else if ( c.parag() == start.parag() && !hadEndParag ) { + c.parag()->setSelection( id, start.index(), c.parag()->length() - 1 ); + } else if ( c.parag() == end.parag() && !hadStartParag ) { + c.parag()->setSelection( id, end.index(), c.parag()->length() - 1 ); + } else if ( c.parag() == end.parag() && hadEndParag ) { + c.parag()->setSelection( id, 0, end.index() ); + } else if ( c.parag() == start.parag() && hadStartParag ) { + c.parag()->setSelection( id, 0, start.index() ); + } else { + c.parag()->setSelection( id, 0, c.parag()->length() - 1 ); + } + } + + if ( leftSelection ) + inSelection = FALSE; + + old = c; + c.gotoNextLetter(); + if ( old == c || noSelectionAnymore ) + break; + } + + if ( !sel.swapped ) + sel.startCursor.parag()->setSelection( id, sel.startCursor.index(), sel.startCursor.parag()->length() - 1 ); + + sel.startCursor = start; + sel.endCursor = end; + if ( sel.startCursor.parag() == sel.endCursor.parag() ) + sel.swapped = sel.startCursor.index() > sel.endCursor.index(); + + setSelectionEndHelper( id, sel, start, end ); + + return TRUE; +} + +void QTextDocument::selectAll( int id ) +{ + removeSelection( id ); + + QTextDocumentSelection sel; + sel.swapped = FALSE; + QTextCursor c( this ); + + c.setParag( fParag ); + c.setIndex( 0 ); + sel.startCursor = c; + + c.setParag( lParag ); + c.setIndex( lParag->length() - 1 ); + sel.endCursor = c; + + QTextParag *p = fParag; + while ( p ) { + p->setSelection( id, 0, p->length() - 1 ); + for ( int i = 0; i < (int)p->length(); ++i ) { + if ( p->at( i )->isCustom() && p->at( i )->customItem()->isNested() ) { + QTextTable *t = (QTextTable*)p->at( i )->customItem(); + QPtrList<QTextTableCell> tableCells = t->tableCells(); + for ( QTextTableCell *c = tableCells.first(); c; c = tableCells.next() ) + c->richText()->selectAll( id ); + } + } + p = p->next(); + } + + selections.insert( id, sel ); +} + +bool QTextDocument::removeSelection( int id ) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return FALSE; + + QTextDocumentSelection &sel = *it; + + if ( sel.startCursor == sel.endCursor ) { + selections.remove( id ); + return TRUE; + } + + if ( !mightHaveCustomItems ) { + QTextCursor start = sel.startCursor; + QTextCursor end = sel.endCursor; + if ( sel.swapped ) { + start = sel.endCursor; + end = sel.startCursor; + } + + for ( QTextParag *p = start.parag(); p; p = p->next() ) { + p->removeSelection( id ); + if ( p == end.parag() ) + break; + } + + selections.remove( id ); + return TRUE; + } + + QTextCursor c( this ); + QTextCursor tmp = sel.startCursor; + if ( sel.swapped ) + tmp = sel.endCursor; + tmp.restoreState(); + c.setParag( tmp.parag() ); + QTextCursor old; + bool hadStart = FALSE; + bool hadEnd = FALSE; + QTextParag *lastParag = 0; + bool leftSelection = FALSE; + bool inSelection = FALSE; + sel.swapped = FALSE; + for ( ;; ) { + if ( c.parag() == sel.startCursor.parag() ) + hadStart = TRUE; + if ( c.parag() == sel.endCursor.parag() ) + hadEnd = TRUE; + + if ( inSelection && + ( c == sel.endCursor && hadStart || c == sel.startCursor && hadEnd ) ) + leftSelection = TRUE; + else if ( !leftSelection && !inSelection && ( c.parag() == sel.startCursor.parag() || c.parag() == sel.endCursor.parag() ) ) + inSelection = TRUE; + + bool noSelectionAnymore = leftSelection && !inSelection && !c.parag()->hasSelection( id ) && c.atParagEnd(); + + if ( lastParag != c.parag() ) + c.parag()->removeSelection( id ); + + old = c; + lastParag = c.parag(); + c.gotoNextLetter(); + if ( old == c || noSelectionAnymore ) + break; + } + + selections.remove( id ); + return TRUE; +} + +QString QTextDocument::selectedText( int id, bool withCustom ) const +{ + // ######## TODO: look at textFormat() and return rich text or plain text (like the text() method!) + QMap<int, QTextDocumentSelection>::ConstIterator it = selections.find( id ); + if ( it == selections.end() ) + return QString::null; + + QTextDocumentSelection sel = *it; + + + QTextCursor c1 = sel.startCursor; + QTextCursor 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 for 3.0.4. + */ + while ( c2.nestedDepth() > c1.nestedDepth() ) + c2.oneUp(); + while ( c1.nestedDepth() > c2.nestedDepth() ) + c1.oneUp(); + while ( c1.nestedDepth() && c2.nestedDepth() && + c1.parag()->document() != c2.parag()->document() ) { + c1.oneUp(); + c2.oneUp(); + } + // do not trust sel_swapped with tables. Fix this properly for 3.0.4 as well + if ( c1.parag()->paragId() > c2.parag()->paragId() || + (c1.parag() == c2.parag() && c1.index() > c2.index() ) ) { + QTextCursor tmp = c1; + c2 = c1; + c1 = tmp; + } + + // end selection 3.0.3 improvement + + + if ( c1.parag() == c2.parag() ) { + QString s; + QTextParag *p = c1.parag(); + int end = c2.index(); + if ( p->at( QMAX( 0, end - 1 ) )->isCustom() ) + ++end; + if ( !withCustom || !p->mightHaveCustomItems ) { + s += p->string()->toString().mid( c1.index(), end - c1.index() ); + } else { + for ( int i = c1.index(); i < end; ++i ) { + if ( p->at( i )->isCustom() ) { + if ( p->at( i )->customItem()->isNested() ) { + s += "\n"; + QTextTable *t = (QTextTable*)p->at( i )->customItem(); + QPtrList<QTextTableCell> cells = t->tableCells(); + for ( QTextTableCell *c = cells.first(); c; c = cells.next() ) + s += c->richText()->plainText() + "\n"; + s += "\n"; + } + } else { + s += p->at( i )->c; + } + } + } + return s; + } + + QString s; + QTextParag *p = c1.parag(); + int start = c1.index(); + while ( p ) { + int end = p == c2.parag() ? c2.index() : p->length() - 1; + if ( p == c2.parag() && p->at( QMAX( 0, end - 1 ) )->isCustom() ) + ++end; + if ( !withCustom || !p->mightHaveCustomItems ) { + s += p->string()->toString().mid( start, end - start ); + if ( p != c2.parag() ) + s += "\n"; + } else { + for ( int i = start; i < end; ++i ) { + if ( p->at( i )->isCustom() ) { + if ( p->at( i )->customItem()->isNested() ) { + s += "\n"; + QTextTable *t = (QTextTable*)p->at( i )->customItem(); + QPtrList<QTextTableCell> cells = t->tableCells(); + for ( QTextTableCell *c = cells.first(); c; c = cells.next() ) + s += c->richText()->plainText() + "\n"; + s += "\n"; + } + } else { + s += p->at( i )->c; + } + } + } + start = 0; + if ( p == c2.parag() ) + break; + p = p->next(); + } + return s; +} + +void QTextDocument::setFormat( int id, QTextFormat *f, int flags ) +{ + QMap<int, QTextDocumentSelection>::ConstIterator it = selections.find( id ); + if ( it == selections.end() ) + return; + + QTextDocumentSelection sel = *it; + + QTextCursor c1 = sel.startCursor; + QTextCursor c2 = sel.endCursor; + if ( sel.swapped ) { + c2 = sel.startCursor; + c1 = sel.endCursor; + } + + c2.restoreState(); + c1.restoreState(); + + if ( c1.parag() == c2.parag() ) { + c1.parag()->setFormat( c1.index(), c2.index() - c1.index(), f, TRUE, flags ); + return; + } + + c1.parag()->setFormat( c1.index(), c1.parag()->length() - c1.index(), f, TRUE, flags ); + QTextParag *p = c1.parag()->next(); + while ( p && p != c2.parag() ) { + p->setFormat( 0, p->length(), f, TRUE, flags ); + p = p->next(); + } + c2.parag()->setFormat( 0, c2.index(), f, TRUE, flags ); +} + +void QTextDocument::copySelectedText( int id ) +{ +#ifndef QT_NO_CLIPBOARD + if ( !hasSelection( id ) ) + return; + + QApplication::clipboard()->setText( selectedText( id ) ); +#endif +} + +void QTextDocument::removeSelectedText( int id, QTextCursor *cursor ) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return; + + QTextDocumentSelection sel = *it; + + QTextCursor c1 = sel.startCursor; + QTextCursor 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.parag() == c2.parag() ) { + c1.parag()->remove( c1.index(), c2.index() - c1.index() ); + return; + } + + if ( c1.parag() == fParag && c1.index() == 0 && + c2.parag() == lParag && c2.index() == lParag->length() - 1 ) + cursor->setValid( FALSE ); + + bool didGoLeft = FALSE; + if ( c1.index() == 0 && c1.parag() != fParag ) { + cursor->gotoPreviousLetter(); + if ( cursor->isValid() ) + didGoLeft = TRUE; + } + + c1.parag()->remove( c1.index(), c1.parag()->length() - 1 - c1.index() ); + QTextParag *p = c1.parag()->next(); + int dy = 0; + QTextParag *tmp; + while ( p && p != c2.parag() ) { + tmp = p->next(); + dy -= p->rect().height(); + delete p; + p = tmp; + } + c2.parag()->remove( 0, c2.index() ); + while ( p ) { + p->move( dy ); + p->invalidate( 0 ); + p->setEndState( -1 ); + p = p->next(); + } + + c1.parag()->join( c2.parag() ); + + if ( didGoLeft ) + cursor->gotoNextLetter(); +} + +void QTextDocument::indentSelection( int id ) +{ + QMap<int, QTextDocumentSelection>::Iterator it = selections.find( id ); + if ( it == selections.end() ) + return; + + QTextDocumentSelection sel = *it; + QTextParag *startParag = sel.startCursor.parag(); + QTextParag *endParag = sel.endCursor.parag(); + if ( sel.endCursor.parag()->paragId() < sel.startCursor.parag()->paragId() ) { + endParag = sel.startCursor.parag(); + startParag = sel.endCursor.parag(); + } + + QTextParag *p = startParag; + while ( p && p != endParag ) { + p->indent(); + p = p->next(); + } +} + +void QTextDocument::addCommand( QTextCommand *cmd ) +{ + commandHistory->addCommand( cmd ); +} + +QTextCursor *QTextDocument::undo( QTextCursor *c ) +{ + return commandHistory->undo( c ); +} + +QTextCursor *QTextDocument::redo( QTextCursor *c ) +{ + return commandHistory->redo( c ); +} + +bool QTextDocument::find( const QString &expr, bool cs, bool wo, bool forward, + int *parag, int *index, QTextCursor *cursor ) +{ + QTextParag *p = forward ? fParag : lParag; + if ( parag ) + p = paragAt( *parag ); + else if ( cursor ) + p = cursor->parag(); + bool first = TRUE; + + while ( p ) { + QString s = p->string()->toString(); + s.remove( s.length() - 1, 1 ); // get rid of trailing space + int start = forward ? 0 : s.length() - 1; + if ( first && index ) + start = *index; + else if ( first ) + start = cursor->index(); + if ( !forward && first ) { + start -= expr.length() + 1; + if ( start < 0 ) { + first = FALSE; + p = p->prev(); + continue; + } + } + first = FALSE; + + for ( ;; ) { + int res = forward ? s.find( expr, start, cs ) : s.findRev( expr, start, cs ); + if ( res == -1 ) + break; + + bool ok = TRUE; + if ( wo ) { + int end = res + expr.length(); + if ( ( res == 0 || s[ res - 1 ].isSpace() || s[ res - 1 ].isPunct() ) && + ( end == (int)s.length() || s[ end ].isSpace() || s[ end ].isPunct() ) ) + ok = TRUE; + else + ok = FALSE; + } + if ( ok ) { + cursor->setParag( p ); + cursor->setIndex( res ); + setSelectionStart( Standard, cursor ); + cursor->setIndex( res + expr.length() ); + setSelectionEnd( Standard, cursor ); + if ( parag ) + *parag = p->paragId(); + if ( index ) + *index = res; + return TRUE; + } + if ( forward ) { + start = res + 1; + } else { + if ( res == 0 ) + break; + start = res - 1; + } + } + p = forward ? p->next() : p->prev(); + } + + return FALSE; +} + +void QTextDocument::setTextFormat( Qt::TextFormat f ) +{ + txtFormat = f; + if ( txtFormat == Qt::RichText && fParag && fParag == lParag && fParag->length() <= 1 ) { + QPtrVector<QStyleSheetItem> v = fParag->styleSheetItems(); + v.resize( v.size() + 1 ); + v.insert( v.size() - 1, styleSheet()->item( "p" ) ); + fParag->setStyleSheetItems( v ); + } + +} + +Qt::TextFormat QTextDocument::textFormat() const +{ + return txtFormat; +} + +bool QTextDocument::inSelection( int selId, const QPoint &pos ) const +{ + QMap<int, QTextDocumentSelection>::ConstIterator it = selections.find( selId ); + if ( it == selections.end() ) + return FALSE; + + QTextDocumentSelection sel = *it; + QTextParag *startParag = sel.startCursor.parag(); + QTextParag *endParag = sel.endCursor.parag(); + if ( sel.startCursor.parag() == sel.endCursor.parag() && + sel.startCursor.parag()->selectionStart( selId ) == sel.endCursor.parag()->selectionEnd( selId ) ) + return FALSE; + if ( sel.endCursor.parag()->paragId() < sel.startCursor.parag()->paragId() ) { + endParag = sel.startCursor.parag(); + startParag = sel.endCursor.parag(); + } + + QTextParag *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 QTextDocument::doLayout( QPainter *p, int w ) +{ + minw = wused = 0; + if ( !is_printer( p ) ) + p = 0; + withoutDoubleBuffer = ( p != 0 ); + QPainter * oldPainter = QTextFormat::painter(); + QTextFormat::setPainter( p ); + flow_->setWidth( w ); + cw = w; + vw = w; + QTextParag *parag = fParag; + while ( parag ) { + parag->invalidate( 0 ); + if ( p ) + parag->adjustToPainter( p ); + parag->format(); + parag = parag->next(); + } + QTextFormat::setPainter( oldPainter ); +} + +QPixmap *QTextDocument::bufferPixmap( const QSize &s ) +{ + if ( !buf_pixmap ) { + int w = QABS( s.width() ); + int h = QABS( s.height() ); + buf_pixmap = new QPixmap( w, h ); + } else { + if ( buf_pixmap->width() < s.width() || + buf_pixmap->height() < s.height() ) { + buf_pixmap->resize( QMAX( s.width(), buf_pixmap->width() ), + QMAX( s.height(), buf_pixmap->height() ) ); + } + } + + return buf_pixmap; +} + +void QTextDocument::draw( QPainter *p, const QRect &rect, const QColorGroup &cg, const QBrush *paper ) +{ + if ( !firstParag() ) + return; + + if ( paper ) { + p->setBrushOrigin( 0, 0 ); + + p->fillRect( rect, *paper ); + } + + if ( formatCollection()->defaultFormat()->color() != cg.text() ) { + QDict<QTextFormat> formats = formatCollection()->dict(); + QDictIterator<QTextFormat> it( formats ); + while ( it.current() ) { + if ( it.current() == formatCollection()->defaultFormat() ) { + ++it; + continue; + } + it.current()->setColor( cg.text() ); + ++it; + } + formatCollection()->defaultFormat()->setColor( cg.text() ); + } + + QTextParag *parag = firstParag(); + 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, cg, 0, FALSE, rect.x(), rect.y(), rect.width(), rect.height() ); + else + parag->paint( *p, cg, 0, FALSE ); + p->translate( 0, -y ); + parag = parag->next(); + if ( !flow()->isEmpty() ) + flow()->drawFloatingItems( p, rect.x(), rect.y(), rect.width(), rect.height(), cg, FALSE ); + } +} + +void QTextDocument::drawParag( QPainter *p, QTextParag *parag, int cx, int cy, int cw, int ch, + QPixmap *&doubleBuffer, const QColorGroup &cg, + bool drawCursor, QTextCursor *cursor, bool resetChanged ) +{ + QPainter *painter = 0; + if ( resetChanged ) + parag->setChanged( FALSE ); + QRect ir( parag->rect() ); + bool useDoubleBuffer = !parag->document()->parent(); + if ( !useDoubleBuffer && parag->document()->nextDoubleBuffered ) + useDoubleBuffer = TRUE; + if ( is_printer( p ) ) + useDoubleBuffer = FALSE; + + if ( useDoubleBuffer ) { + painter = new QPainter; + if ( cx >= 0 && cy >= 0 ) + ir = ir.intersect( QRect( cx, cy, cw, ch ) ); + if ( !doubleBuffer || + ir.width() > doubleBuffer->width() || + ir.height() > doubleBuffer->height() ) { + doubleBuffer = bufferPixmap( ir.size() ); + painter->begin( doubleBuffer ); + } else { + painter->begin( doubleBuffer ); + } + } else { + painter = p; + painter->translate( ir.x(), ir.y() ); + } + + painter->setBrushOrigin( -ir.x(), -ir.y() ); + + if ( useDoubleBuffer || is_printer( painter ) ) { + if ( !parag->backgroundColor() ) + painter->fillRect( QRect( 0, 0, ir.width(), ir.height() ), + cg.brush( QColorGroup::Base ) ); + else + painter->fillRect( QRect( 0, 0, ir.width(), ir.height() ), + *parag->backgroundColor() ); + } else { + if ( cursor && cursor->parag() == parag ) { + if ( !parag->backgroundColor() ) + painter->fillRect( QRect( parag->at( cursor->index() )->x, 0, 2, ir.height() ), + cg.brush( QColorGroup::Base ) ); + else + painter->fillRect( QRect( parag->at( cursor->index() )->x, 0, 2, ir.height() ), + *parag->backgroundColor() ); + } + } + + painter->translate( -( ir.x() - parag->rect().x() ), + -( ir.y() - parag->rect().y() ) ); + parag->paint( *painter, cg, drawCursor ? cursor : 0, TRUE, cx, cy, cw, ch ); + + if ( useDoubleBuffer ) { + delete painter; + painter = 0; + p->drawPixmap( ir.topLeft(), *doubleBuffer, QRect( QPoint( 0, 0 ), ir.size() ) ); + } else { + painter->translate( -ir.x(), -ir.y() ); + } + + if ( parag->rect().x() + parag->rect().width() < parag->document()->x() + parag->document()->width() ) { + p->fillRect( parag->rect().x() + parag->rect().width(), parag->rect().y(), + ( parag->document()->x() + parag->document()->width() ) - + ( parag->rect().x() + parag->rect().width() ), + parag->rect().height(), cg.brush( QColorGroup::Base ) ); + } + + parag->document()->nextDoubleBuffered = FALSE; +} + +QTextParag *QTextDocument::draw( QPainter *p, int cx, int cy, int cw, int ch, const QColorGroup &cg, + bool onlyChanged, bool drawCursor, QTextCursor *cursor, bool resetChanged ) +{ + if ( withoutDoubleBuffer || par && par->withoutDoubleBuffer ) { + withoutDoubleBuffer = TRUE; + QRect r; + draw( p, r, cg ); + return 0; + } + withoutDoubleBuffer = FALSE; + + if ( !firstParag() ) + return 0; + + if ( drawCursor && cursor ) + tmpCursor = cursor; + if ( cx < 0 && cy < 0 ) { + cx = 0; + cy = 0; + cw = width(); + ch = height(); + } + + QTextParag *lastFormatted = 0; + QTextParag *parag = firstParag(); + + QPixmap *doubleBuffer = 0; + QPainter painter; + + while ( parag ) { + lastFormatted = parag; + if ( !parag->isValid() ) + parag->format(); + + if ( !parag->rect().intersects( QRect( cx, cy, cw, ch ) ) ) { + QRect pr( parag->rect() ); + pr.setWidth( parag->document()->width() ); + if ( pr.intersects( QRect( cx, cy, cw, ch ) ) ) + p->fillRect( pr.intersect( QRect( cx, cy, cw, ch ) ), cg.brush( QColorGroup::Base ) ); + if ( parag->rect().y() > cy + ch ) { + tmpCursor = 0; + goto floating; + } + parag = parag->next(); + continue; + } + + if ( !parag->hasChanged() && onlyChanged ) { + parag = parag->next(); + continue; + } + + drawParag( p, parag, cx, cy, cw, ch, doubleBuffer, cg, drawCursor, cursor, resetChanged ); + parag = parag->next(); + } + + parag = lastParag(); + + floating: + if ( parag->rect().y() + parag->rect().height() < parag->document()->height() ) { + if ( !parag->document()->parent() ) { // !useDoubleBuffer + p->fillRect( 0, parag->rect().y() + parag->rect().height(), parag->document()->width(), + parag->document()->height() - ( parag->rect().y() + parag->rect().height() ), + cg.brush( QColorGroup::Base ) ); + } + if ( !flow()->isEmpty() ) { + QRect cr( cx, cy, cw, ch ); +// cr = cr.intersect( QRect( 0, parag->rect().y() + parag->rect().height(), parag->document()->width(), +// parag->document()->height() - ( parag->rect().y() + parag->rect().height() ) ) ); + flow()->drawFloatingItems( p, cr.x(), cr.y(), cr.width(), cr.height(), cg, FALSE ); + } + } + + if ( buf_pixmap && buf_pixmap->height() > 300 ) { + delete buf_pixmap; + buf_pixmap = 0; + } + + tmpCursor = 0; + return lastFormatted; +} + +void QTextDocument::setDefaultFont( const QFont &f ) +{ + int s = f.pointSize(); + bool usePixels = FALSE; + if ( s == -1 ) { + s = f.pixelSize(); + usePixels = TRUE; + } + updateFontSizes( s, usePixels ); +} + +void QTextDocument::registerCustomItem( QTextCustomItem *i, QTextParag *p ) +{ + if ( i && i->placement() != QTextCustomItem::PlaceInline ) { + flow_->registerFloatingItem( i ); + p->registerFloatingItem( i ); + i->setParagraph( p ); + } + p->mightHaveCustomItems = mightHaveCustomItems = TRUE; +} + +void QTextDocument::unregisterCustomItem( QTextCustomItem *i, QTextParag *p ) +{ + flow_->unregisterFloatingItem( i ); + p->unregisterFloatingItem( i ); + i->setParagraph( 0 ); +} + +bool QTextDocument::hasFocusParagraph() const +{ + return !!focusIndicator.parag; +} + +QString QTextDocument::focusHref() const +{ + return focusIndicator.href; +} + +bool QTextDocument::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 = QString::null; + + if ( next ) { + QTextParag *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(); + while ( i < p->length() ) { + if ( !p->at( i )->isAnchor() ) + return TRUE; + focusIndicator.len++; + i++; + } + } else if ( p->at( i )->isCustom() ) { + if ( p->at( i )->customItem()->isNested() ) { + QTextTable *t = (QTextTable*)p->at( i )->customItem(); + QPtrList<QTextTableCell> cells = t->tableCells(); + // first try to continue + QTextTableCell *c; + bool resetCells = TRUE; + for ( c = cells.first(); c; c = cells.next() ) { + 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(); + return TRUE; + } else { + resetCells = FALSE; + c = cells.next(); + break; + } + } + } + // now really try + if ( resetCells ) + c = cells.first(); + for ( ; c; c = cells.next() ) { + if ( c->richText()->focusNextPrevChild( next ) ) { + p->setChanged( TRUE ); + focusIndicator.parag = p; + focusIndicator.start = i; + focusIndicator.len = 0; + focusIndicator.href = c->richText()->focusHref(); + return TRUE; + } + } + } + } + } + index = 0; + p = p->next(); + } + } else { + QTextParag *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(); + while ( i >= -1 ) { + if ( i < 0 || !p->at( i )->isAnchor() ) { + focusIndicator.start++; + return TRUE; + } + if ( i < 0 ) + break; + focusIndicator.len++; + focusIndicator.start--; + i--; + } + } else if ( p->at( i )->isCustom() ) { + if ( p->at( i )->customItem()->isNested() ) { + QTextTable *t = (QTextTable*)p->at( i )->customItem(); + QPtrList<QTextTableCell> cells = t->tableCells(); + // first try to continue + QTextTableCell *c; + bool resetCells = TRUE; + for ( c = cells.last(); c; c = cells.prev() ) { + 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(); + return TRUE; + } else { + resetCells = FALSE; + c = cells.prev(); + break; + } + } + if ( cells.at() == 0 ) + break; + } + // now really try + if ( resetCells ) + c = cells.last(); + for ( ; c; c = cells.prev() ) { + if ( c->richText()->focusNextPrevChild( next ) ) { + p->setChanged( TRUE ); + focusIndicator.parag = p; + focusIndicator.start = i; + focusIndicator.len = 0; + focusIndicator.href = c->richText()->focusHref(); + return TRUE; + } + if ( cells.at() == 0 ) + break; + } + } + } + } + p = p->prev(); + if ( p ) + index = p->length() - 1; + } + } + + focusIndicator.parag = 0; + + return FALSE; +} + +int QTextDocument::length() const +{ + int l = 0; + QTextParag *p = fParag; + while ( p ) { + l += p->length() - 1; // don't count trailing space + p = p->next(); + } + return l; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +int QTextFormat::width( const QChar &c ) const +{ + if ( c.unicode() == 0xad ) // soft hyphen + return 0; + if ( !pntr || !pntr->isActive() ) { + if ( c == '\t' ) + return fm.width( 'x' ) * 8; + 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 ); + ( (QTextFormat*)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 ); + } + pntr->setFont( f ); + + return pntr->fontMetrics().width( c ); +} + +int QTextFormat::width( const QString &str, int pos ) const +{ + int w = 0; + if ( str[ pos ].unicode() == 0xad ) + return w; + if ( !pntr || !pntr->isActive() ) { + if ( ha == AlignNormal ) { + w = fm.width( 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_.width( str[ pos ] ); + } + } else { + QFont f( fn ); + if ( ha != AlignNormal ) { + if ( usePixelSizes ) + f.setPixelSize( ( f.pixelSize() * 2 ) / 3 ); + else + f.setPointSize( ( f.pointSize() * 2 ) / 3 ); + } + pntr->setFont( f ); + w = pntr->fontMetrics().width( str[ pos ] ); + } + return w; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextString::QTextString() +{ + bidiDirty = FALSE; + bidi = FALSE; + rightToLeft = FALSE; + dir = QChar::DirON; +} + +QTextString::QTextString( const QTextString &s ) +{ + bidiDirty = s.bidiDirty; + bidi = s.bidi; + rightToLeft = s.rightToLeft; + dir = s.dir; + data = s.subString(); +} + +void QTextString::insert( int index, const QString &s, QTextFormat *f ) +{ + int os = data.size(); + data.resize( data.size() + s.length() ); + if ( index < os ) { + memmove( data.data() + index + s.length(), data.data() + index, + sizeof( QTextStringChar ) * ( os - index ) ); + } + for ( int i = 0; i < (int)s.length(); ++i ) { + data[ (int)index + i ].x = 0; + data[ (int)index + i ].lineStart = 0; + data[ (int)index + i ].d.format = 0; + data[ (int)index + i ].type = QTextStringChar::Regular; + data[ (int)index + i ].rightToLeft = 0; + data[ (int)index + i ].startOfRun = 0; + data[ (int)index + i ].c = s[ i ]; + data[ (int)index + i ].setFormat( f ); + } + bidiDirty = TRUE; +} + +QTextString::~QTextString() +{ + clear(); +} + +void QTextString::insert( int index, QTextStringChar *c ) +{ + int os = data.size(); + data.resize( data.size() + 1 ); + if ( index < os ) { + memmove( data.data() + index + 1, data.data() + index, + sizeof( QTextStringChar ) * ( os - index ) ); + } + data[ (int)index ].c = c->c; + data[ (int)index ].x = 0; + data[ (int)index ].lineStart = 0; + data[ (int)index ].rightToLeft = 0; + data[ (int)index ].d.format = 0; + data[ (int)index ].type = QTextStringChar::Regular; + data[ (int)index ].setFormat( c->format() ); + bidiDirty = TRUE; +} + +void QTextString::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 ) { + if ( !(data[ i ].type == QTextStringChar::Regular) ) { + delete data[ i ].customItem(); + if ( data[ i ].d.custom->format ) + data[ i ].d.custom->format->removeRef(); + delete data[ i ].d.custom; + data[ i ].d.custom = 0; + } else if ( data[ i ].format() ) { + data[ i ].format()->removeRef(); + } + } + } + data.truncate( index ); + bidiDirty = TRUE; +} + +void QTextString::remove( int index, int len ) +{ + for ( int i = index; i < (int)data.size() && i - index < len; ++i ) { + if ( !(data[ i ].type == QTextStringChar::Regular) ) { + delete data[ i ].customItem(); + if ( data[ i ].d.custom->format ) + data[ i ].d.custom->format->removeRef(); + delete data[ i ].d.custom; + data[ i ].d.custom = 0; + } else if ( data[ i ].format() ) { + data[ i ].format()->removeRef(); + } + } + memmove( data.data() + index, data.data() + index + len, + sizeof( QTextStringChar ) * ( data.size() - index - len ) ); + data.resize( data.size() - len ); + bidiDirty = TRUE; +} + +void QTextString::clear() +{ + for ( int i = 0; i < (int)data.count(); ++i ) { + if ( !(data[ i ].type == QTextStringChar::Regular) ) { + delete data[ i ].customItem(); + if ( data[ i ].d.custom->format ) + data[ i ].d.custom->format->removeRef(); + delete data[ i ].d.custom; + data[ i ].d.custom = 0; + } else if ( data[ i ].format() ) { + data[ i ].format()->removeRef(); + } + } + data.resize( 0 ); +} + +void QTextString::setFormat( int index, QTextFormat *f, bool useCollection ) +{ + if ( useCollection && data[ index ].format() ) + data[ index ].format()->removeRef(); + data[ index ].setFormat( f ); +} + +void QTextString::checkBidi() const +{ + bool rtlKnown = FALSE; + if ( dir == QChar::DirR ) { + ((QTextString *)this)->bidi = TRUE; + ((QTextString *)this)->rightToLeft = TRUE; + rtlKnown = TRUE; + return; + } else if ( dir == QChar::DirL ) { + ((QTextString *)this)->rightToLeft = FALSE; + rtlKnown = TRUE; + } else { + ((QTextString *)this)->rightToLeft = FALSE; + } + + int len = data.size(); + const QTextStringChar *c = data.data(); + ((QTextString *)this)->bidi = FALSE; + while( len ) { + if ( !rtlKnown ) { + switch( c->c.direction() ) + { + case QChar::DirL: + case QChar::DirLRO: + case QChar::DirLRE: + ((QTextString *)this)->rightToLeft = FALSE; + rtlKnown = TRUE; + break; + case QChar::DirR: + case QChar::DirAL: + case QChar::DirRLO: + case QChar::DirRLE: + ((QTextString *)this)->rightToLeft = TRUE; + rtlKnown = TRUE; + break; + default: + break; + } + } + uchar row = c->c.row(); + if( (row > 0x04 && row < 0x09) || (row > 0xfa && row < 0xff) ) { + ((QTextString *)this)->bidi = TRUE; + if ( rtlKnown ) + return; + } + len--; + ++c; + } +} + +void QTextDocument::setStyleSheet( QStyleSheet *s ) +{ + if ( !s ) + return; + sheet_ = s; + fCollection->setStyleSheet( s ); + updateStyles(); +} + +void QTextDocument::updateStyles() +{ + invalidate(); + if ( par ) + underlLinks = par->underlLinks; + fCollection->updateStyles(); + for ( QTextDocument *d = childList.first(); d; d = childList.next() ) + d->updateStyles(); +} + +void QTextDocument::updateFontSizes( int base, bool usePixels ) +{ + for ( QTextDocument *d = childList.first(); d; d = childList.next() ) + d->updateFontSizes( base, usePixels ); + invalidate(); + fCollection->updateFontSizes( base, usePixels ); +} + +void QTextDocument::updateFontAttributes( const QFont &f, const QFont &old ) +{ + for ( QTextDocument *d = childList.first(); d; d = childList.next() ) + d->updateFontAttributes( f, old ); + invalidate(); + fCollection->updateFontAttributes( f, old ); +} + +void QTextStringChar::setFormat( QTextFormat *f ) +{ + if ( type == Regular ) { + d.format = f; + } else { + if ( !d.custom ) { + d.custom = new CustomData; + d.custom->custom = 0; + } + d.custom->format = f; + } +} + +void QTextStringChar::setCustomItem( QTextCustomItem *i ) +{ + if ( type == Regular ) { + QTextFormat *f = format(); + d.custom = new CustomData; + d.custom->format = f; + } else { + delete d.custom->custom; + } + d.custom->custom = i; + type = (type == Anchor ? CustomAnchor : Custom); +} + +void QTextStringChar::loseCustomItem() +{ + if ( type == Custom ) { + QTextFormat *f = d.custom->format; + d.custom->custom = 0; + delete d.custom; + type = Regular; + d.format = f; + } else if ( type == CustomAnchor ) { + d.custom->custom = 0; + type = Anchor; + } +} + +QString QTextStringChar::anchorName() const +{ + if ( type == Regular ) + return QString::null; + else + return d.custom->anchorName; +} + +QString QTextStringChar::anchorHref() const +{ + if ( type == Regular ) + return QString::null; + else + return d.custom->anchorHref; +} + +void QTextStringChar::setAnchor( const QString& name, const QString& href ) +{ + if ( type == Regular ) { + QTextFormat *f = format(); + d.custom = new CustomData; + d.custom->custom = 0; + d.custom->format = f; + type = Anchor; + } else if ( type == Custom ) { + type = CustomAnchor; + } + d.custom->anchorName = name; + d.custom->anchorHref = href; +} + + +int QTextString::width( int idx ) const +{ + int w = 0; + QTextStringChar *c = &at( idx ); + if ( c->c.unicode() == 0xad ) + return 0; + if( c->isCustom() ) { + if( c->customItem()->placement() == QTextCustomItem::PlaceInline ) + w = c->customItem()->width; + } else { + int r = c->c.row(); + if( r < 0x06 || r > 0x1f ) + w = c->format()->width( c->c ); + else { + // complex text. We need some hacks to get the right metric here + QString str; + int pos = 0; + if( idx > 4 ) + pos = idx - 4; + int off = idx - pos; + int end = QMIN( length(), idx + 4 ); + while ( pos < end ) { + str += at(pos).c; + pos++; + } + w = c->format()->width( str, off ); + } + } + return w; +} + +QMemArray<QTextStringChar> QTextString::subString( int start, int len ) const +{ + if ( len == 0xFFFFFF ) + len = data.size(); + QMemArray<QTextStringChar> a; + a.resize( len ); + for ( int i = 0; i < len; ++i ) { + QTextStringChar *c = &data[ i + start ]; + a[ i ].c = c->c; + a[ i ].x = 0; + a[ i ].lineStart = 0; + a[ i ].rightToLeft = 0; + a[ i ].d.format = 0; + a[ i ].type = QTextStringChar::Regular; + a[ i ].setFormat( c->format() ); + if ( c->format() ) + c->format()->addRef(); + } + return a; +} + +QTextStringChar *QTextStringChar::clone() const +{ + QTextStringChar *chr = new QTextStringChar; + chr->c = c; + chr->x = 0; + chr->lineStart = 0; + chr->rightToLeft = 0; + chr->d.format = 0; + chr->type = QTextStringChar::Regular; + chr->setFormat( format() ); + if ( chr->format() ) + chr->format()->addRef(); + return chr; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextParag::QTextParag( QTextDocument *d, QTextParag *pr, QTextParag *nx, bool updateIds ) + : invalid( 0 ), p( pr ), n( nx ), docOrPseudo( d ), align( 0 ),mSelections( 0 ), + mStyleSheetItemsVec( 0 ), mFloatingItems( 0 ), listS( QStyleSheetItem::ListDisc ), + numSubParag( -1 ), tm( -1 ), bm( -1 ), lm( -1 ), rm( -1 ), flm( -1 ), + tArray(0), tabStopWidth(0), eData( 0 ) +{ + listS = QStyleSheetItem::ListDisc; + if ( ! (hasdoc = docOrPseudo != 0 ) ) + docOrPseudo = new QTextParagPseudoDocument; + bgcol = 0; + breakable = TRUE; + isBr = FALSE; + movedDown = FALSE; + mightHaveCustomItems = FALSE; + visible = TRUE; + list_val = -1; + newLinesAllowed = FALSE; + lastInFrame = FALSE; + defFormat = formatCollection()->defaultFormat(); + if ( !hasdoc ) { + tabStopWidth = defFormat->width( 'x' ) * 8; + pseudoDocument()->commandHistory = new QTextCommandHistory( 100 ); + } +#if defined(PARSER_DEBUG) + qDebug( debug_indent + "new QTextParag" ); +#endif + fullWidth = TRUE; + + if ( p ) + p->n = this; + if ( n ) + n->p = this; + + + if ( !p && hasdoc ) + document()->setFirstParag( this ); + if ( !n && hasdoc ) + document()->setLastParag( this ); + + changed = FALSE; + firstFormat = TRUE; + state = -1; + needPreProcess = FALSE; + + if ( p ) + id = p->id + 1; + else + id = 0; + if ( n && updateIds ) { + QTextParag *s = n; + while ( s ) { + s->id = s->p->id + 1; + s->numSubParag = -1; + s->lm = s->rm = s->tm = s->bm = -1, s->flm = -1; + s = s->n; + } + } + firstPProcess = TRUE; + + str = new QTextString(); + str->insert( 0, " ", formatCollection()->defaultFormat() ); +} + +QTextParag::~QTextParag() +{ + delete str; + if ( hasdoc ) { + register QTextDocument *doc = document(); + if ( this == doc->minwParag ) { + doc->minwParag = 0; + doc->minw = 0; + } + if ( this == doc->curParag ) + doc->curParag = 0; + } else { + delete pseudoDocument(); + } + if ( tArray ) + delete [] tArray; + delete eData; + QMap<int, QTextParagLineStart*>::Iterator it = lineStarts.begin(); + for ( ; it != lineStarts.end(); ++it ) + delete *it; + if ( mSelections ) + delete mSelections; + if ( mFloatingItems ) + delete mFloatingItems; + if ( mStyleSheetItemsVec ) + delete mStyleSheetItemsVec; + if ( p ) + p->setNext( n ); + if ( n ) + n->setPrev( p ); +} + +void QTextParag::setNext( QTextParag *s ) +{ + n = s; + if ( !n && hasdoc ) + document()->setLastParag( this ); +} + +void QTextParag::setPrev( QTextParag *s ) +{ + p = s; + if ( !p && hasdoc ) + document()->setFirstParag( this ); +} + +void QTextParag::invalidate( int chr ) +{ + if ( invalid < 0 ) + invalid = chr; + else + invalid = QMIN( invalid, chr ); + if ( mFloatingItems ) { + for ( QTextCustomItem *i = mFloatingItems->first(); i; i = mFloatingItems->next() ) + i->ypos = -1; + } + lm = rm = bm = tm = flm = -1; +} + +void QTextParag::insert( int index, const QString &s ) +{ + if ( hasdoc && !document()->useFormatCollection() && document()->preProcessor() ) + str->insert( index, s, + document()->preProcessor()->format( QTextPreProcessor::Standard ) ); + else + str->insert( index, s, formatCollection()->defaultFormat() ); + invalidate( index ); + needPreProcess = TRUE; +} + +void QTextParag::truncate( int index ) +{ + str->truncate( index ); + insert( length(), " " ); + needPreProcess = TRUE; +} + +void QTextParag::remove( int index, int len ) +{ + if ( index + len - str->length() > 0 ) + return; + for ( int i = index; i < index + len; ++i ) { + QTextStringChar *c = at( i ); + if ( hasdoc && c->isCustom() ) { + document()->unregisterCustomItem( c->customItem(), this ); + } + } + str->remove( index, len ); + invalidate( 0 ); + needPreProcess = TRUE; +} + +void QTextParag::join( QTextParag *s ) +{ + int oh = r.height() + s->r.height(); + n = s->n; + if ( n ) + n->p = this; + else if ( hasdoc ) + document()->setLastParag( this ); + + int start = str->length(); + if ( length() > 0 && at( length() - 1 )->c == ' ' ) { + 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 ); + } + if ( s->str->at( i ).isCustom() ) { + QTextCustomItem * item = s->str->at( i ).customItem(); + str->at( i + start ).setCustomItem( item ); + s->str->at( i ).loseCustomItem(); + } + if ( s->str->at( i ).isAnchor() ) { + str->at( i + start ).setAnchor( s->str->at( i ).anchorName(), + s->str->at( i ).anchorHref() ); + } + } + + 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 ) { + QTextParag *s = n; + while ( s ) { + s->id = s->p->id + 1; + s->state = -1; + s->needPreProcess = TRUE; + s->changed = TRUE; + s = s->n; + } + } + format(); + state = -1; +} + +void QTextParag::move( int &dy ) +{ + if ( dy == 0 ) + return; + changed = TRUE; + r.moveBy( 0, dy ); + if ( mFloatingItems ) { + for ( QTextCustomItem *i = mFloatingItems->first(); i; i = mFloatingItems->next() ) + i->ypos += dy; + } + 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 QTextParag::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() ) ); + r.setWidth( documentWidth() ); + if ( p ) + p->lastInFrame = FALSE; + + movedDown = FALSE; + bool formattedAgain = FALSE; + + formatAgain: + + if ( hasdoc && mFloatingItems ) { + for ( QTextCustomItem *i = mFloatingItems->first(); i; i = mFloatingItems->next() ) { + i->ypos = r.y(); + if ( i->placement() == QTextCustomItem::PlaceRight ) { + i->xpos = r.x() + r.width() - i->width; + } + } + } + QMap<int, QTextParagLineStart*> oldLineStarts = lineStarts; + lineStarts.clear(); + int y = formatter()->format( document(), this, start, oldLineStarts ); + + + r.setWidth( QMAX( r.width(), formatter()->minimumWidth() ) ); + + + QMap<int, QTextParagLineStart*>::Iterator it = oldLineStarts.begin(); + + for ( ; it != oldLineStarts.end(); ++it ) + delete *it; + + QTextStringChar *c = 0; + // do not do this on mac, as the paragraph + // with has to be the full document width on mac as the selections + // always extend completely to the right. This is a bit unefficient, + // as this results in a bigger double buffer than needed but ok for + // now. + if ( lineStarts.count() == 1 ) { //&& ( !doc || document()->flow()->isEmpty() ) ) { + if ( !string()->isBidi() ) { + c = &str->at( str->length() - 1 ); + r.setWidth( c->x + str->width( str->length() - 1 ) ); + } else { + r.setWidth( lineStarts[0]->w ); + } + } + + if ( newLinesAllowed ) { + 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 = 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(); + QTextParag *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 QTextParag::lineHeightOfChar( int i, int *bl, int *y ) const +{ + if ( !isValid() ) + ( (QTextParag*)this )->format(); + + QMap<int, QTextParagLineStart*>::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( "QTextParag::lineHeightOfChar: couldn't find lh for %d", i ); + return 15; +} + +QTextStringChar *QTextParag::lineStartOfChar( int i, int *index, int *line ) const +{ + if ( !isValid() ) + ( (QTextParag*)this )->format(); + + int l = (int)lineStarts.count() - 1; + QMap<int, QTextParagLineStart*>::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( "QTextParag::lineStartOfChar: couldn't find %d", i ); + return 0; +} + +int QTextParag::lines() const +{ + if ( !isValid() ) + ( (QTextParag*)this )->format(); + + return (int)lineStarts.count(); +} + +QTextStringChar *QTextParag::lineStartOfLine( int line, int *index ) const +{ + if ( !isValid() ) + ( (QTextParag*)this )->format(); + + if ( line >= 0 && line < (int)lineStarts.count() ) { + QMap<int, QTextParagLineStart*>::ConstIterator it = lineStarts.begin(); + while ( line-- > 0 ) + ++it; + int i = it.key(); + if ( index ) + *index = i; + return &str->at( i ); + } + + qWarning( "QTextParag::lineStartOfLine: couldn't find %d", line ); + return 0; +} + +int QTextParag::leftGap() const +{ + if ( !isValid() ) + ( (QTextParag*)this )->format(); + + int line = 0; + int x = str->at(0).x; /* set x to x of first char */ + if ( str->isBidi() ) { + for ( int i = 1; i < str->length(); ++i ) + x = QMIN(x, str->at(i).x); + return x; + } + + QMap<int, QTextParagLineStart*>::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 QTextParag::setFormat( int index, int len, QTextFormat *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; + + QTextFormatCollection *fc = 0; + if ( useCollection ) + fc = formatCollection(); + QTextFormat *of; + for ( int i = 0; i < len; ++i ) { + of = str->at( i + index ).format(); + if ( !changed && 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 == QTextFormat::Format || !fc ) { + if ( fc ) + f = fc->format( f ); + str->setFormat( i + index, f, useCollection ); + } else { + QTextFormat *fm = fc->format( of, f, flags ); + str->setFormat( i + index, fm, useCollection ); + } + } +} + +void QTextParag::indent( int *oldIndent, int *newIndent ) +{ + if ( !hasdoc || !document()->indent() || style() && style()->displayMode() != QStyleSheetItem::DisplayBlock ) { + if ( oldIndent ) + *oldIndent = 0; + if ( newIndent ) + *newIndent = 0; + if ( oldIndent && newIndent ) + *newIndent = *oldIndent; + return; + } + document()->indent()->indent( document(), this, oldIndent, newIndent ); +} + +void QTextParag::paint( QPainter &painter, const QColorGroup &cg, QTextCursor *cursor, bool drawSelections, + int clipx, int clipy, int clipw, int cliph ) +{ + if ( !visible ) + return; + QTextStringChar *chr = at( 0 ); + int i = 0; + int h = 0; + int baseLine = 0, lastBaseLine = 0; + QTextStringChar *formatChar = 0; + int lastY = -1; + int startX = 0; + int bw = 0; + int cy = 0; + int curx = -1, cury = 0, curh = 0; + bool lastDirection = chr->rightToLeft; + const int full_sel_width = (hasdoc ? document()->width() : r.width()); +#if 0 // seems we don't need that anymore + int tw = 0; +#endif + + QString qstr = str->toString(); + // ### 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 ( uint ii = 0; ii < qstr.length(); ii++ ) { + if ( uc[(int)ii]== '\n' ) + uc[(int)ii] = 0x20; + } + + + const int nSels = hasdoc ? document()->numSelections() : 1; + QMemArray<int> selectionStarts( nSels ); + QMemArray<int> selectionEnds( nSels ); + if ( drawSelections ) { + bool hasASelection = FALSE; + for ( i = 0; i < nSels; ++i ) { + if ( !hasSelection( i ) ) { + selectionStarts[ i ] = -1; + selectionEnds[ i ] = -1; + } else { + hasASelection = TRUE; + selectionStarts[ i ] = selectionStart( i ); + int end = selectionEnd( i ); + if ( end == length() - 1 && n && n->hasSelection( i ) ) + end++; + selectionEnds[ i ] = end; + } + } + if ( !hasASelection ) + drawSelections = FALSE; + } + + int line = -1; + int cw; + bool didListLabel = FALSE; + int paintStart = 0; + int paintEnd = -1; + int lasth = 0; + for ( i = 0; i < length(); i++ ) { + chr = at( i ); +#if 0 // seems we don't need that anymore + if ( !str->isBidi() && is_printer( &painter ) ) { // ### fix our broken ps-printer + if ( !chr->lineStart ) + chr->x = QMAX( chr->x, tw ); + else + tw = 0; + } +#endif + cw = string()->width( i ); + if ( chr->c == '\t' && i < length() - 1 ) + cw = at( i + 1 )->x - chr->x + 1; + if ( chr->c.unicode() == 0xad && i < length() - 1 ) + cw = 0; + + // init a new line + if ( chr->lineStart ) { +#if 0 // seems we don't need that anymore + tw = 0; +#endif + ++line; + lineInfo( line, cy, h, baseLine ); + lasth = h; + if ( clipy != -1 && cy > clipy - r.y() + cliph ) // outside clip area, leave + break; + if ( lastBaseLine == 0 ) + lastBaseLine = baseLine; + } + + // draw bullet list items + if ( !didListLabel && line == 0 && style() && style()->displayMode() == QStyleSheetItem::DisplayListItem ) { + didListLabel = TRUE; + drawLabel( &painter, chr->x, cy, 0, 0, baseLine, cg ); + } + + // check for cursor mark + if ( cursor && this == cursor->parag() && i == cursor->index() ) { + curx = cursor->x(); + QTextStringChar *c = chr; + if ( i > 0 ) + --c; + curh = c->format()->height(); + cury = cy + baseLine - c->format()->ascent(); + } + + // first time - start again... + if ( !formatChar || lastY == -1 ) { + formatChar = chr; + lastY = cy; + startX = chr->x; + if ( !chr->isCustom() && chr->c != '\n' ) + paintEnd = i; + bw = cw; + if ( !chr->isCustom() ) + continue; + } + + // check if selection state changed + bool selectionChange = FALSE; + if ( drawSelections ) { + for ( int j = 0; j < nSels; ++j ) { + selectionChange = selectionStarts[ j ] == i || selectionEnds[ j ] == i; + if ( selectionChange ) + break; + } + } + + //if something (format, etc.) changed, draw what we have so far + if ( ( ( ( alignment() & Qt3::AlignJustify ) == Qt3::AlignJustify && at(paintEnd)->c.isSpace() ) || + lastDirection != (bool)chr->rightToLeft || + chr->startOfRun || + lastY != cy || chr->format() != formatChar->format() || chr->isAnchor() != formatChar->isAnchor() || + ( paintEnd != -1 && at( paintEnd )->c =='\t' ) || chr->c == '\t' || + ( paintEnd != -1 && at( paintEnd )->c.unicode() == 0xad ) || chr->c.unicode() == 0xad || + selectionChange || chr->isCustom() ) ) { + if ( paintStart <= paintEnd ) { + // ### temporary hack until I get the new placement/shaping stuff working + int x = startX; + if ( ( alignment() & Qt3::AlignJustify ) == Qt3::AlignJustify && paintEnd != -1 && + paintEnd > 1 && at( paintEnd )->c.isSpace() ) { + int add = str->at(paintEnd).x - str->at(paintEnd-1).x - str->width(paintEnd-1); + bw += ( lastDirection ? 0 : add ); + } + drawParagString( painter, qstr, paintStart, paintEnd - paintStart + 1, x, lastY, + lastBaseLine, bw, lasth, drawSelections, + formatChar, i, selectionStarts, selectionEnds, cg, lastDirection ); + } +#if 0 // seems we don't need that anymore + if ( !str->isBidi() && is_printer( &painter ) ) { // ### fix our broken ps-printer + if ( !chr->lineStart ) { + // ### the next line doesn't look 100% correct for arabic + tw = startX + painter.fontMetrics().width( qstr.mid(paintStart, paintEnd - paintStart +1) ); + chr->x = QMAX( chr->x, tw ); + } else { + tw = 0; + } + } +#endif + if ( !chr->isCustom() ) { + if ( chr->c != '\n' ) { + paintStart = i; + paintEnd = i; + } else { + paintStart = i+1; + paintEnd = -1; + } + formatChar = chr; + lastY = cy; + startX = chr->x; + bw = cw; + } else { + if ( chr->customItem()->placement() == QTextCustomItem::PlaceInline ) { + chr->customItem()->draw( &painter, chr->x, cy, clipx - r.x(), clipy - r.y(), clipw, cliph, cg, + nSels && selectionStarts[ 0 ] <= i && selectionEnds[ 0 ] >= i ); + paintStart = i+1; + paintEnd = -1; + formatChar = chr; + lastY = cy; + startX = chr->x + string()->width( i ); + bw = 0; + } else { + chr->customItem()->resize( chr->customItem()->width ); + paintStart = i+1; + paintEnd = -1; + formatChar = chr; + lastY = cy; + startX = chr->x + string()->width( i ); + bw = 0; + } + } + } else { + if ( chr->c != '\n' ) { + if( chr->rightToLeft ) { + startX = chr->x; + } + paintEnd = i; + } + bw += cw; + } + lastBaseLine = baseLine; + lasth = h; + lastDirection = chr->rightToLeft; + } + + // if we are through the parag, but still have some stuff left to draw, draw it now + if ( paintStart <= paintEnd ) { + bool selectionChange = FALSE; + if ( drawSelections ) { + for ( int j = 0; j < nSels; ++j ) { + selectionChange = selectionStarts[ j ] == i || selectionEnds[ j ] == i; + if ( selectionChange ) + break; + } + } + int x = startX; + drawParagString( painter, qstr, paintStart, paintEnd-paintStart+1, x, lastY, + lastBaseLine, bw, h, drawSelections, + formatChar, i, selectionStarts, selectionEnds, cg, lastDirection ); + } + + // if we should draw a cursor, draw it now + if ( curx != -1 && cursor ) { + painter.fillRect( QRect( curx, cury, 1, curh - lineSpacing() ), cg.color( QColorGroup::Text ) ); + painter.save(); + if ( string()->isBidi() ) { + const int d = 4; + if ( at( cursor->index() )->rightToLeft ) { + painter.setPen( Qt::black ); + painter.drawLine( curx, cury, curx - d / 2, cury + d / 2 ); + painter.drawLine( curx, cury + d, curx - d / 2, cury + d / 2 ); + } else { + painter.setPen( Qt::black ); + painter.drawLine( curx, cury, curx + d / 2, cury + d / 2 ); + painter.drawLine( curx, cury + d, curx + d / 2, cury + d / 2 ); + } + } + painter.restore(); + } +} + +//#define BIDI_DEBUG + +void QTextParag::drawParagString( QPainter &painter, const QString &s, int start, int len, int startX, + int lastY, int baseLine, int bw, int h, bool drawSelections, + QTextStringChar *formatChar, int i, const QMemArray<int> &selectionStarts, + const QMemArray<int> &selectionEnds, const QColorGroup &cg, bool rightToLeft ) +{ + bool plainText = hasdoc ? document()->textFormat() == Qt::PlainText : FALSE; + QTextFormat* format = formatChar->format(); + QString str( s ); + if ( str[ (int)str.length() - 1 ].unicode() == 0xad ) + str.remove( str.length() - 1, 1 ); + if ( !plainText || hasdoc && format->color() != document()->formatCollection()->defaultFormat()->color() ) + painter.setPen( QPen( format->color() ) ); + else + painter.setPen( cg.text() ); + painter.setFont( format->font() ); + + if ( hasdoc && formatChar->isAnchor() && !formatChar->anchorHref().isEmpty() ) { + if ( format->useLinkColor() ) { + if ( document()->linkColor.isValid() ) + painter.setPen( document()->linkColor ); + else + painter.setPen( QPen( Qt::blue ) ); + } + if ( document()->underlineLinks() ) { + QFont fn = format->font(); + fn.setUnderline( TRUE ); + painter.setFont( fn ); + } + } + + if ( drawSelections ) { + const int nSels = hasdoc ? document()->numSelections() : 1; + const int startSel = is_printer( 0 ) ? 1 : 0; + for ( int j = startSel; j < nSels; ++j ) { + if ( i > selectionStarts[ j ] && i <= selectionEnds[ j ] ) { + if ( !hasdoc || document()->invertSelectionText( j ) ) + painter.setPen( QPen( cg.color( QColorGroup::HighlightedText ) ) ); + if ( j == QTextDocument::Standard ) + painter.fillRect( startX, lastY, bw, h, cg.color( QColorGroup::Highlight ) ); + else + painter.fillRect( startX, lastY, bw, h, hasdoc ? document()->selectionColor( j ) : cg.color( QColorGroup::Highlight ) ); + } + } + } + + if ( str[ start ] != '\t' && str[ start ].unicode() != 0xad ) { + if ( format->vAlign() == QTextFormat::AlignNormal ) { + painter.drawText( startX, lastY + baseLine, str.mid( start ), len ); +#ifdef BIDI_DEBUG + painter.save(); + painter.setPen ( Qt::red ); + painter.drawLine( startX, lastY, startX, lastY + baseLine ); + painter.drawLine( startX, lastY + baseLine/2, startX + 10, lastY + baseLine/2 ); + int w = 0; + int i = 0; + while( i < len ) + w += painter.fontMetrics().charWidth( str, start + i++ ); + painter.setPen ( Qt::blue ); + painter.drawLine( startX + w - 1, lastY, startX + w - 1, lastY + baseLine ); + painter.drawLine( startX + w - 1, lastY + baseLine/2, startX + w - 1 - 10, lastY + baseLine/2 ); + painter.restore(); +#endif + } else if ( format->vAlign() == QTextFormat::AlignSuperScript ) { + QFont f( painter.font() ); + if ( format->fontSizesInPixels() ) + f.setPixelSize( ( f.pixelSize() * 2 ) / 3 ); + else + f.setPointSize( ( f.pointSize() * 2 ) / 3 ); + painter.setFont( f ); + painter.drawText( startX, lastY + baseLine - ( painter.fontMetrics().height() / 2 ), + str.mid( start ), len ); + } else if ( format->vAlign() == QTextFormat::AlignSubScript ) { + QFont f( painter.font() ); + if ( format->fontSizesInPixels() ) + f.setPixelSize( ( f.pixelSize() * 2 ) / 3 ); + else + f.setPointSize( ( f.pointSize() * 2 ) / 3 ); + painter.setFont( f ); + painter.drawText( startX, lastY + baseLine + painter.fontMetrics().height() / 6, str.mid( start ), len ); + } + } + if ( i + 1 < length() && at( i + 1 )->lineStart && at( i )->c.unicode() == 0xad ) { + painter.drawText( startX + bw, lastY + baseLine, "\xad" ); + } + if ( format->isMisspelled() ) { + painter.save(); + painter.setPen( QPen( Qt::red, 1, Qt::DotLine ) ); + painter.drawLine( startX, lastY + baseLine + 1, startX + bw, lastY + baseLine + 1 ); + painter.restore(); + } + + i -= len; + + if ( hasdoc && formatChar->isAnchor() && !formatChar->anchorHref().isEmpty() && + document()->focusIndicator.parag == this && + ( document()->focusIndicator.start >= i && + document()->focusIndicator.start + document()->focusIndicator.len <= i + len || + document()->focusIndicator.start <= i && + document()->focusIndicator.start + document()->focusIndicator.len >= i + len ) ) { + painter.drawWinFocusRect( QRect( startX, lastY, bw, h ) ); + } + +} + +void QTextParag::drawLabel( QPainter* p, int x, int y, int w, int h, int base, const QColorGroup& cg ) +{ + if ( !style() ) + return; + QRect r ( x, y, w, h ); + QStyleSheetItem::ListStyle s = listStyle(); + + p->save(); + p->setPen( defFormat->color() ); + + QFont font2( defFormat->font() ); + if ( length() > 0 ) { + QTextFormat *format = at( 0 )->format(); + if ( format ) { + if ( format->fontSizesInPixels() ) + font2.setPixelSize( at( 0 )->format()->font().pixelSize() ); + else + font2.setPointSize( at( 0 )->format()->font().pointSize() ); + } + } + p->setFont( font2 ); + QFontMetrics fm( p->fontMetrics() ); + int size = fm.lineSpacing() / 3; + + switch ( s ) { + case QStyleSheetItem::ListDecimal: + case QStyleSheetItem::ListLowerAlpha: + case QStyleSheetItem::ListUpperAlpha: + { + int n = numberOfSubParagraph(); + QString l; + switch ( s ) { + case QStyleSheetItem::ListLowerAlpha: + if ( n < 27 ) { + l = QChar( ('a' + (char) (n-1))); + break; + } + case QStyleSheetItem::ListUpperAlpha: + if ( n < 27 ) { + l = QChar( ('A' + (char) (n-1))); + break; + } + break; + default: //QStyleSheetItem::ListDecimal: + l.setNum( n ); + break; + } + l += QString::fromLatin1(". "); + p->drawText( r.right() - fm.width( l ), r.top() + base, l ); + } + break; + case QStyleSheetItem::ListSquare: + { + QRect er( r.right() - size * 2, r.top() + fm.height() / 2 - size / 2, size, size ); + p->fillRect( er , cg.brush( QColorGroup::Foreground ) ); + } + break; + case QStyleSheetItem::ListCircle: + { + QRect er( r.right()-size*2, r.top() + fm.height() / 2 - size / 2, size, size); + p->drawEllipse( er ); + } + break; + case QStyleSheetItem::ListDisc: + default: + { + p->setBrush( cg.brush( QColorGroup::Foreground )); + QRect er( r.right()-size*2, r.top() + fm.height() / 2 - size / 2, size, size); + p->drawEllipse( er ); + p->setBrush( Qt::NoBrush ); + } + break; + } + + p->restore(); +} + +void QTextParag::setStyleSheetItems( const QPtrVector<QStyleSheetItem> &vec ) +{ + styleSheetItemsVec() = vec; + invalidate( 0 ); + lm = rm = tm = bm = flm = -1; + numSubParag = -1; +} + +void QTextParag::setList( bool b, int listStyle ) +{ + if ( !hasdoc ) + return; + + if ( !style() ) { + styleSheetItemsVec().resize( 2 ); + mStyleSheetItemsVec->insert( 0, document()->styleSheet()->item( "html" ) ); + mStyleSheetItemsVec->insert( 1, document()->styleSheet()->item( "p" ) ); + } + + if ( b ) { + if ( style()->displayMode() != QStyleSheetItem::DisplayListItem || this->listStyle() != listStyle ) { + styleSheetItemsVec().remove( styleSheetItemsVec().size() - 1 ); + QStyleSheetItem *item = (*mStyleSheetItemsVec)[ mStyleSheetItemsVec->size() - 1 ]; + if ( item ) + mStyleSheetItemsVec->remove( mStyleSheetItemsVec->size() - 1 ); + mStyleSheetItemsVec->insert( mStyleSheetItemsVec->size() - 1, + listStyle == QStyleSheetItem::ListDisc || listStyle == QStyleSheetItem::ListCircle + || listStyle == QStyleSheetItem::ListSquare ? + document()->styleSheet()->item( "ul" ) : document()->styleSheet()->item( "ol" ) ); + mStyleSheetItemsVec->insert( mStyleSheetItemsVec->size() - 1, document()->styleSheet()->item( "li" ) ); + setListStyle( (QStyleSheetItem::ListStyle)listStyle ); + } else { + return; + } + } else { + if ( style()->displayMode() != QStyleSheetItem::DisplayBlock ) { + styleSheetItemsVec().remove( styleSheetItemsVec().size() - 1 ); + if ( mStyleSheetItemsVec->size() >= 2 ) { + mStyleSheetItemsVec->remove( mStyleSheetItemsVec->size() - 2 ); + mStyleSheetItemsVec->resize( mStyleSheetItemsVec->size() - 2 ); + } else { + mStyleSheetItemsVec->resize( mStyleSheetItemsVec->size() - 1 ); + } + } else { + return; + } + } + invalidate( 0 ); + lm = rm = tm = bm = flm = -1; + numSubParag = -1; + if ( next() ) { + QTextParag *s = next(); + while ( s ) { + s->numSubParag = -1; + s->lm = s->rm = s->tm = s->bm = flm = -1; + s->numSubParag = -1; + s->invalidate( 0 ); + s = s->next(); + } + } +} + +void QTextParag::incDepth() +{ + if ( !style() || !hasdoc ) + return; + if ( style()->displayMode() != QStyleSheetItem::DisplayListItem ) + return; + styleSheetItemsVec().resize( styleSheetItemsVec().size() + 1 ); + mStyleSheetItemsVec->insert( mStyleSheetItemsVec->size() - 1, (*mStyleSheetItemsVec)[ mStyleSheetItemsVec->size() - 2 ] ); + mStyleSheetItemsVec->insert( mStyleSheetItemsVec->size() - 2, + listStyle() == QStyleSheetItem::ListDisc || listStyle() == QStyleSheetItem::ListCircle || + listStyle() == QStyleSheetItem::ListSquare ? + document()->styleSheet()->item( "ul" ) : document()->styleSheet()->item( "ol" ) ); + invalidate( 0 ); + lm = -1; + flm = -1; +} + +void QTextParag::decDepth() +{ + if ( !style() || !hasdoc ) + return; + if ( style()->displayMode() != QStyleSheetItem::DisplayListItem ) + return; + int numLists = 0; + QStyleSheetItem *lastList = 0; + int lastIndex = 0; + int i; + if ( mStyleSheetItemsVec ) { + for ( i = 0; i < (int)mStyleSheetItemsVec->size(); ++i ) { + QStyleSheetItem *item = (*mStyleSheetItemsVec)[ i ]; + if ( item->name() == "ol" || item->name() == "ul" ) { + lastList = item; + lastIndex = i; + numLists++; + } + } + } + + if ( !lastList ) + return; + styleSheetItemsVec().remove( lastIndex ); + for ( i = lastIndex; i < (int)mStyleSheetItemsVec->size() - 1; ++i ) + mStyleSheetItemsVec->insert( i, (*mStyleSheetItemsVec)[ i + 1 ] ); + mStyleSheetItemsVec->resize( mStyleSheetItemsVec->size() - 1 ); + if ( numLists == 1 ) + setList( FALSE, -1 ); + invalidate( 0 ); + lm = -1; + flm = -1; +} + +int QTextParag::listDepth() const +{ + int numLists = 0; + int i; + if ( mStyleSheetItemsVec ) { + for ( i = 0; i < (int)mStyleSheetItemsVec->size(); ++i ) { + QStyleSheetItem *item = (*mStyleSheetItemsVec)[ i ]; + if ( item->name() == "ol" || item->name() == "ul" ) + numLists++; + } + } + return numLists - 1; +} + +int *QTextParag::tabArray() const +{ + int *ta = tArray; + if ( !ta && hasdoc ) + ta = document()->tabArray(); + return ta; +} + +int QTextParag::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 d; + if ( tabStopWidth != 0 ) + d = x / tabStopWidth; + else + return x; + return tabStopWidth * ( d + 1 ); + } +} + +void QTextParag::adjustToPainter( QPainter *p ) +{ + for ( int i = 0; i < length(); ++i ) { + if ( at( i )->isCustom() ) + at( i )->customItem()->adjustToPainter( p ); + } +} + +QTextFormatCollection *QTextParag::formatCollection() const +{ + if ( hasdoc ) + return document()->formatCollection(); + if ( !qFormatCollection ) { + qFormatCollection = new QTextFormatCollection; + static QSingleCleanupHandler<QTextFormatCollection> qtfCleanup; + qtfCleanup.set( &qFormatCollection ); + } + return qFormatCollection; +} + +QString QTextParag::richText() const +{ + QString s; + QTextStringChar *formatChar = 0; + QString spaces; + bool lastCharWasSpace = FALSE; + int firstcol = 0; + for ( int i = 0; i < length()-1; ++i ) { + QTextStringChar *c = &str->at( i ); + if ( c->isAnchor() && !c->anchorName().isEmpty() ) { + if ( c->anchorName().contains( '#' ) ) { + QStringList l = QStringList::split( '#', c->anchorName() ); + for ( QStringList::ConstIterator it = l.begin(); it != l.end(); ++it ) + s += "<a name=\"" + *it + "\"></a>"; + } else { + s += "<a name=\"" + c->anchorName() + "\"></a>"; + } + } + if ( !formatChar ) { + s += c->format()->makeFormatChangeTags( 0, QString::null, c->anchorHref() ); + formatChar = c; + } else if ( ( formatChar->format()->key() != c->format()->key() ) || + (formatChar->isAnchor() != c->isAnchor() && + (!c->anchorHref().isEmpty() || !formatChar->anchorHref().isEmpty() ) ) ) {// lisp was here + + if ( !spaces.isEmpty() ) { + if ( spaces[0] == '\t' || lastCharWasSpace ) + s += "<wsp>" + spaces + "</wsp>"; + else if ( spaces.length() > 1 ) + s += "<wsp>" + spaces.mid(1) + "</wsp> "; + else + s += spaces; + lastCharWasSpace = TRUE; + spaces = QString::null; + } + s += c->format()->makeFormatChangeTags( formatChar->format() , formatChar->anchorHref(), c->anchorHref() ); + formatChar = c; + } + + if ( c->c == ' ' || c->c == '\t' ) { + spaces += c->c; + continue; + } else if ( !spaces.isEmpty() ) { + if ( spaces[0] == '\t' || lastCharWasSpace ) + s += "<wsp>" + spaces + "</wsp>"; + else if ( spaces.length() > 1 ) + s += "<wsp>" + spaces.mid(1) + "</wsp> "; + else + s += spaces; + spaces = QString::null; + if ( s.length() - firstcol > 60 ) { + s += '\n'; + firstcol = s.length(); + } + } + + lastCharWasSpace = FALSE; + if ( c->c == '<' ) { + s += "<"; + } else if ( c->c == '>' ) { + s += ">"; + } else if ( c->isCustom() ) { + s += c->customItem()->richText(); + } else { + s += c->c; + } + } + if ( !spaces.isEmpty() ) { + if ( spaces.length() > 1 || spaces[0] == '\t' || lastCharWasSpace ) + s += "<wsp>" + spaces + "</wsp>"; + else + s += spaces; + } + + if ( formatChar ) + s += formatChar->format()->makeFormatEndTags( formatChar->anchorHref() ); + return s; +} + +void QTextParag::addCommand( QTextCommand *cmd ) +{ + if ( !hasdoc ) + pseudoDocument()->commandHistory->addCommand( cmd ); + else + document()->commands()->addCommand( cmd ); +} + +QTextCursor *QTextParag::undo( QTextCursor *c ) +{ + if ( !hasdoc ) + return pseudoDocument()->commandHistory->undo( c ); + return document()->commands()->undo( c ); +} + +QTextCursor *QTextParag::redo( QTextCursor *c ) +{ + if ( !hasdoc ) + return pseudoDocument()->commandHistory->redo( c ); + return document()->commands()->redo( c ); +} + +int QTextParag::topMargin() const +{ + if ( !p && ( !hasdoc || !document()->addMargins() ) ) + return 0; + if ( tm != -1 ) + return tm; + QStyleSheetItem *item = style(); + if ( !item ) { + ( (QTextParag*)this )->tm = 0; + return 0; + } + + int m = 0; + if ( item->margin( QStyleSheetItem::MarginTop ) != QStyleSheetItem::Undefined ) + m = item->margin( QStyleSheetItem::MarginTop ); + if ( mStyleSheetItemsVec ) { + QStyleSheetItem *it = 0; + QStyleSheetItem *p = prev() ? prev()->style() : 0; + for ( int i = (int)mStyleSheetItemsVec->size() - 2 ; i >= 0; --i ) { + it = (*mStyleSheetItemsVec)[ i ]; + if ( it != p ) + break; + int mar = it->margin( QStyleSheetItem::MarginTop ); + m += (mar != QStyleSheetItem::Undefined) ? mar : 0; + if ( it->displayMode() != QStyleSheetItem::DisplayInline ) + break; + } + } + m = scale( m, QTextFormat::painter() ); + + ( (QTextParag*)this )->tm = m; + return tm; +} + +int QTextParag::bottomMargin() const +{ + if ( bm != -1 ) + return bm; + QStyleSheetItem *item = style(); + if ( !item || !next() ) { + ( (QTextParag*)this )->bm = 0; + return 0; + } + + int m = 0; + if ( item->margin( QStyleSheetItem::MarginBottom ) != QStyleSheetItem::Undefined ) + m = item->margin( QStyleSheetItem::MarginBottom ); + if ( mStyleSheetItemsVec ) { + QStyleSheetItem *it = 0; + QStyleSheetItem *n = next() ? next()->style() : 0; + for ( int i =(int)mStyleSheetItemsVec->size() - 2 ; i >= 0; --i ) { + it = (*mStyleSheetItemsVec)[ i ]; + if ( it != n ) + break; + int mar = it->margin( QStyleSheetItem::MarginBottom ); + m += mar != QStyleSheetItem::Undefined ? mar : 0; + if ( it->displayMode() != QStyleSheetItem::DisplayInline ) + break; + } + } + m = scale ( m, QTextFormat::painter() ); + + ( (QTextParag*)this )->bm = m; + return bm; +} + +int QTextParag::leftMargin() const +{ + if ( lm != -1 ) + return lm; + QStyleSheetItem *item = style(); + if ( !item ) { + ( (QTextParag*)this )->lm = 0; + return 0; + } + int m = 0; + if ( mStyleSheetItemsVec ) { + for ( int i = 0; i < (int)mStyleSheetItemsVec->size(); ++i ) { + item = (*mStyleSheetItemsVec)[ i ]; + int mar = item->margin( QStyleSheetItem::MarginLeft ); + m += mar != QStyleSheetItem::Undefined ? mar : 0; + if ( item->name() == "ol" || item->name() == "ul" ) { + QPainter* oldPainter = QTextFormat::painter(); + QTextFormat::setPainter( 0 ); + m += defFormat->width( '1' ) + + defFormat->width( '2' ) + + defFormat->width( '3' ) + + defFormat->width( '.' ); + QTextFormat::setPainter( oldPainter ); + } + } + } + + m = scale ( m, QTextFormat::painter() ); + + ( (QTextParag*)this )->lm = m; + return lm; +} + +int QTextParag::firstLineMargin() const +{ + if ( flm != -1 ) + return lm; + QStyleSheetItem *item = style(); + if ( !item ) { + ( (QTextParag*)this )->flm = 0; + return 0; + } + int m = 0; + if ( mStyleSheetItemsVec ) { + for ( int i = 0; i < (int)mStyleSheetItemsVec->size(); ++i ) { + item = (*mStyleSheetItemsVec)[ i ]; + int mar = item->margin( QStyleSheetItem::MarginFirstLine ); + m += mar != QStyleSheetItem::Undefined ? mar : 0; + } + } + + m = scale( m, QTextFormat::painter() ); + + ( (QTextParag*)this )->flm = m; + return flm; +} + +int QTextParag::rightMargin() const +{ + if ( rm != -1 ) + return rm; + QStyleSheetItem *item = style(); + if ( !item ) { + ( (QTextParag*)this )->rm = 0; + return 0; + } + int m = 0; + if ( mStyleSheetItemsVec ) { + for ( int i = 0; i < (int)mStyleSheetItemsVec->size(); ++i ) { + item = (*mStyleSheetItemsVec)[ i ]; + int mar = item->margin( QStyleSheetItem::MarginRight ); + m += mar != QStyleSheetItem::Undefined ? mar : 0; + } + } + m = scale( m, QTextFormat::painter() ); + + ( (QTextParag*)this )->rm = m; + return rm; +} + +int QTextParag::lineSpacing() const +{ + QStyleSheetItem *item = style(); + if ( !item ) + return 0; + + int ls = item->lineSpacing(); + if ( ls == QStyleSheetItem::Undefined ) + return 0; + ls = scale( ls, QTextFormat::painter() ); + + return ls; +} + +void QTextParag::copyParagData( QTextParag *parag ) +{ + setStyleSheetItems( parag->styleSheetItems() ); + setListStyle( parag->listStyle() ); + setAlignment( parag->alignment() ); + QColor *c = parag->backgroundColor(); + if ( c ) + setBackgroundColor( *c ); +} + +void QTextParag::show() +{ + if ( visible || !hasdoc ) + return; + visible = TRUE; +} + +void QTextParag::hide() +{ + if ( !visible || !hasdoc ) + return; + visible = FALSE; +} + +void QTextParag::setDirection( QChar::Direction d ) +{ + if ( str && str->direction() != d ) { + str->setDirection( d ); + invalidate( 0 ); + } +} + +QChar::Direction QTextParag::direction() const +{ + return (str ? str->direction() : QChar::DirON ); +} + +void QTextParag::setChanged( bool b, bool recursive ) +{ + changed = b; + if ( recursive ) { + if ( document() && document()->parentParag() ) + document()->parentParag()->setChanged( b, recursive ); + } +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +QTextPreProcessor::QTextPreProcessor() +{ +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextFormatter::QTextFormatter() + : thisminw(0), thiswused(0), wrapEnabled( TRUE ), wrapColumn( -1 ), biw( FALSE ) +{ +} + +/* only used for bidi or complex text reordering + */ +QTextParagLineStart *QTextFormatter::formatLine( QTextParag *parag, QTextString *string, QTextParagLineStart *line, + QTextStringChar *startChar, QTextStringChar *lastChar, int align, int space ) +{ +#ifndef QT_NO_COMPLEXTEXT + if( string->isBidi() ) + return bidiReorderLine( parag, string, line, startChar, lastChar, align, space ); +#endif + space = QMAX( space, 0 ); // #### with nested tables this gets negative because of a bug I didn't find yet, so workaround for now. This also means non-left aligned nested tables do not work at the moment + int start = (startChar - &string->at(0)); + int last = (lastChar - &string->at(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 & Qt3::AlignJustify ) { + int numSpaces = 0; + for ( int j = start; j < last; ++j ) { + if( isBreakable( string, j ) ) { + numSpaces++; + } + } + int toAdd = 0; + for ( int k = start + 1; k <= last; ++k ) { + if( isBreakable( string, k ) && numSpaces ) { + 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 QTextParagLineStart(); +} + +#ifndef QT_NO_COMPLEXTEXT + +#ifdef BIDI_DEBUG +#include <iostream> +#endif + +// collects one line of the paragraph and transforms it to visual order +QTextParagLineStart *QTextFormatter::bidiReorderLine( QTextParag * /*parag*/, QTextString *text, QTextParagLineStart *line, + QTextStringChar *startChar, QTextStringChar *lastChar, int align, int space ) +{ + int start = (startChar - &text->at(0)); + int last = (lastChar - &text->at(0) ); + //qDebug("doing BiDi reordering from %d to %d!", start, last); + + QBidiControl *control = new QBidiControl( line->context(), line->status ); + QString str; + str.setUnicode( 0, last - start + 1 ); + // fill string with logically ordered chars. + QTextStringChar *ch = startChar; + QChar *qch = (QChar *)str.unicode(); + while ( ch <= lastChar ) { + *qch = ch->c; + qch++; + ch++; + } + int x = startChar->x; + + QPtrList<QTextRun> *runs; + runs = QComplexText::bidiReorderLine(control, str, 0, last - start + 1, + (text->isRightToLeft() ? QChar::DirR : QChar::DirL) ); + + // now construct the reordered string out of the runs... + + int numSpaces = 0; + // set the correct alignment. This is a bit messy.... + if( align == Qt3::AlignAuto ) { + // align according to directionality of the paragraph... + if ( text->isRightToLeft() ) + align = Qt::AlignRight; + } + + if ( align & Qt::AlignHCenter ) + x += space/2; + else if ( align & Qt::AlignRight ) + x += space; + else if ( align & Qt3::AlignJustify ) { + for ( int j = start; j < last; ++j ) { + if( isBreakable( text, j ) ) { + numSpaces++; + } + } + } + int toAdd = 0; + bool first = TRUE; + QTextRun *r = runs->first(); + int xmax = -0xffffff; + while ( r ) { + if(r->level %2) { + // odd level, need to reverse the string + int pos = r->stop + start; + while(pos >= r->start + start) { + QTextStringChar *c = &text->at(pos); + if( numSpaces && !first && isBreakable( text, pos ) ) { + int s = space / numSpaces; + toAdd += s; + space -= s; + numSpaces--; + } else if ( first ) { + first = FALSE; + if ( c->c == ' ' ) + x -= c->format()->width( ' ' ); + } + c->x = x + toAdd; + c->rightToLeft = TRUE; + c->startOfRun = FALSE; + int ww = 0; + if ( c->c.unicode() >= 32 || c->c == '\t' || c->c == '\n' || c->isCustom() ) { + ww = text->width( pos ); + } else { + ww = c->format()->width( ' ' ); + } + if ( xmax < x + toAdd + ww ) xmax = x + toAdd + ww; + x += ww; + pos--; + } + } else { + int pos = r->start + start; + while(pos <= r->stop + start) { + QTextStringChar* c = &text->at(pos); + if( numSpaces && !first && isBreakable( text, pos ) ) { + int s = space / numSpaces; + toAdd += s; + space -= s; + numSpaces--; + } else if ( first ) { + first = FALSE; + if ( c->c == ' ' ) + x -= c->format()->width( ' ' ); + } + c->x = x + toAdd; + c->rightToLeft = FALSE; + c->startOfRun = FALSE; + int ww = 0; + if ( c->c.unicode() >= 32 || c->c == '\t' || c->isCustom() ) { + ww = text->width( pos ); + } else { + ww = c->format()->width( ' ' ); + } + //qDebug("setting char %d at pos %d", pos, x); + if ( xmax < x + toAdd + ww ) xmax = x + toAdd + ww; + x += ww; + pos++; + } + } + text->at( r->start + start ).startOfRun = TRUE; + r = runs->next(); + } + + line->w = xmax + 10; + QTextParagLineStart *ls = new QTextParagLineStart( control->context, control->status ); + delete control; + delete runs; + return ls; +} +#endif + +bool QTextFormatter::isBreakable( QTextString *string, int pos ) const +{ + const QChar &c = string->at( pos ).c; + char ch = c.latin1(); + if ( c.isSpace() && ch != '\n' && c.unicode() != 0x00a0U ) + return TRUE; + if ( c.unicode() == 0xad ) // soft hyphen + return TRUE; + if ( !ch ) { + // not latin1, need to do more sophisticated checks for other scripts + uchar row = c.row(); + if ( row == 0x0e ) { + // 0e00 - 0e7f == Thai + if ( c.cell() < 0x80 ) { +#ifdef HAVE_THAI_BREAKS + // check for thai + if( string != cachedString ) { + // build up string of thai chars + QTextCodec *thaiCodec = QTextCodec::codecForMib(2259); + if ( !thaiCache ) + thaiCache = new QCString; + if ( !thaiIt ) + thaiIt = ThBreakIterator::createWordInstance(); + *thaiCache = thaiCodec->fromUnicode( s->string() ); + } + thaiIt->setText(thaiCache->data()); + for(int i = thaiIt->first(); i != thaiIt->DONE; i = thaiIt->next() ) { + if( i == pos ) + return TRUE; + if( i > pos ) + return FALSE; + } + return FALSE; +#else + // if we don't have a thai line breaking lib, allow + // breaks everywhere except directly before punctuation. + return TRUE; +#endif + } else + return FALSE; + } + if ( row < 0x11 ) // no asian font + return FALSE; + if ( row > 0x2d && row < 0xfb || row == 0x11 ) + // asian line breaking. Everywhere allowed except directly + // in front of a punctuation character. + return TRUE; + } + return FALSE; +} + +void QTextFormatter::insertLineStart( QTextParag *parag, int index, QTextParagLineStart *ls ) +{ + if ( index > 0 ) { // we can assume that only first line starts are insrted multiple times + parag->lineStartList().insert( index, ls ); + return; + } + QMap<int, QTextParagLineStart*>::Iterator it; + if ( ( it = parag->lineStartList().find( index ) ) == parag->lineStartList().end() ) { + parag->lineStartList().insert( index, ls ); + } else { + delete *it; + parag->lineStartList().remove( it ); + parag->lineStartList().insert( index, ls ); + } +} + + +/* Standard pagebreak algorithm using QTextFlow::adjustFlow. Returns + the shift of the paragraphs bottom line. + */ +int QTextFormatter::formatVertically( QTextDocument* doc, QTextParag* parag ) +{ + int oldHeight = parag->rect().height(); + QMap<int, QTextParagLineStart*>& lineStarts = parag->lineStartList(); + QMap<int, QTextParagLineStart*>::Iterator it = lineStarts.begin(); + int h = doc->addMargins() ? parag->topMargin() : 0; + for ( ; it != lineStarts.end() ; ++it ) { + QTextParagLineStart * ls = it.data(); + ls->y = h; + QTextStringChar *c = ¶g->string()->at(it.key()); + 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 { + 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() && doc && !doc->addMargins() ) + m = QMAX( m, parag->next()->topMargin() ); + if ( parag->next() && parag->next()->isLineBreak() ) + m = 0; + h += m; + parag->setHeight( h ); + return h - oldHeight; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextFormatterBreakInWords::QTextFormatterBreakInWords() +{ +} + +int QTextFormatterBreakInWords::format( QTextDocument *doc,QTextParag *parag, + int start, const QMap<int, QTextParagLineStart*> & ) +{ + QTextStringChar *c = 0; + QTextStringChar *firstChar = 0; + int left = doc ? parag->leftMargin() + doc->leftMargin() : 4; + int x = left + ( doc ? parag->firstLineMargin() : 0 ); + int dw = parag->documentVisibleWidth() - ( doc ? doc->rightMargin() : 0 ); + int y = doc && doc->addMargins() ? parag->topMargin() : 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; + QTextParagLineStart *lineStart = new QTextParagLineStart( y, y, 0 ); + insertLineStart( parag, 0, lineStart ); + + QPainter *painter = QTextFormat::painter(); + + int col = 0; + int ww = 0; + QChar lastChr; + for ( ; i < len; ++i, ++col ) { + if ( c ) + lastChr = c->c; + c = ¶g->string()->at( i ); + c->rightToLeft = FALSE; + // ### 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 == '\t' ) { + int nx = parag->nextTab( i, x - left ) + left; + if ( nx < x ) + ww = w - x; + else + ww = nx - x; + } else { + ww = c->format()->width( ' ' ); + } + + 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 QTextParagLineStart( y, h, h ); + insertLineStart( parag, i, lineStart ); + c->lineStart = 1; + firstChar = c; + x = 0xffffff; + continue; + } + + if ( wrapEnabled && + ( wrapAtColumn() == -1 && x + ww > w || + wrapAtColumn() != -1 && col >= wrapAtColumn() ) || + parag->isNewLinesAllowed() && lastChr == '\n' ) { + 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() && doc && !doc->addMargins() ) + m = QMAX( m, parag->next()->topMargin() ); + parag->setFullWidth( fullWidth ); + if ( parag->next() && parag->next()->isLineBreak() ) + m = 0; + y += h + m; + if ( !wrapEnabled ) + minw = QMAX(minw, wused); + + thisminw = minw; + thiswused = wused; + return y; +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextFormatterBreakWords::QTextFormatterBreakWords() +{ +} + +#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 QTextFormatterBreakWords::format( QTextDocument *doc, QTextParag *parag, + int start, const QMap<int, QTextParagLineStart*> & ) +{ + QTextStringChar *c = 0; + QTextStringChar *firstChar = 0; + QTextString *string = parag->string(); + int left = doc ? parag->leftMargin() + doc->leftMargin() : 0; + int x = left + ( doc ? parag->firstLineMargin() : 0 ); + int y = doc && doc->addMargins() ? parag->topMargin() : 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 linespace = doc ? parag->lineSpacing() : 0; + bool wrapEnabled = isWrapEnabled( parag ); + + start = 0; + if ( start == 0 ) + c = ¶g->string()->at( 0 ); + + int i = start; + QTextParagLineStart *lineStart = new QTextParagLineStart( y, y, 0 ); + insertLineStart( parag, 0, lineStart ); + int lastBreak = -1; + int tmpBaseLine = 0, tmph = 0; + bool lastWasNonInlineCustom = FALSE; + + int align = parag->alignment(); + if ( align == Qt3::AlignAuto && doc && doc->alignment() != Qt3::AlignAuto ) + align = doc->alignment(); + + align &= Qt3::AlignHorizontal_Mask; + + QPainter *painter = QTextFormat::painter(); + int col = 0; + int ww = 0; + QChar lastChr; + for ( ; i < len; ++i, ++col ) { + if ( c ) + lastChr = c->c; + // ### next line should not be needed + if ( painter ) + c->format()->setPainter( painter ); + c = &string->at( i ); + c->rightToLeft = FALSE; + if ( i > 0 && (x > curLeft || ww == 0) || lastWasNonInlineCustom ) { + c->lineStart = 0; + } else { + c->lineStart = 1; + firstChar = c; + } + + if ( c->isCustom() && c->customItem()->placement() != QTextCustomItem::PlaceInline ) + lastWasNonInlineCustom = TRUE; + else + lastWasNonInlineCustom = FALSE; + + if ( c->c.unicode() >= 32 || c->isCustom() ) { + ww = string->width( i ); + } else if ( c->c == '\t' ) { + int nx = parag->nextTab( i, x - left ) + left; + if ( nx < x ) + ww = w - x; + else + ww = nx - x; + } else { + ww = c->format()->width( ' ' ); + } + + // last character ("invisible" space) has no width + if ( i == len - 1 ) + ww = 0; + + QTextCustomItem* ci = c->customItem(); + if ( c->isCustom() && ci->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 ); + QTextParagLineStart *lineStart2 = formatLine( parag, string, lineStart, firstChar, c-1, align, w - x ); + 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, tmph ); + tmph = c->height() + linespace; + h = tmph; + lineStart = lineStart2; + lineStart->y = y; + insertLineStart( parag, i, lineStart ); + c->lineStart = 1; + firstChar = c; + } else { + tmph = c->height() + linespace; + h = tmph; + delete lineStart2; + } + lineStart->h = h; + lineStart->baseLine = h; + tmpBaseLine = lineStart->baseLine; + lastBreak = -2; + x = 0xffffff; + minw = QMAX( minw, tminw ); + + int tw = ci->minimumWidth(); + if ( tw < QWIDGETSIZE_MAX ) + tminw = tw; + else + tminw = marg; + wused = QMAX( wused, ci->width ); + continue; + } else if ( c->isCustom() && ci->placement() != QTextCustomItem::PlaceInline ) { + int tw = ci->minimumWidth(); + if ( tw < QWIDGETSIZE_MAX ) + minw = QMAX( minw, tw ); + } + + if ( wrapEnabled && ( !c->c.isSpace() || lastBreak == -2 ) + && ( lastBreak != -1 || allowBreakInWords() ) && + ( wrapAtColumn() == -1 && x + ww > w && lastBreak != -1 || + wrapAtColumn() == -1 && x + ww > w - 4 && lastBreak == -1 && allowBreakInWords() || + wrapAtColumn() != -1 && col >= wrapAtColumn() ) || + parag->isNewLinesAllowed() && lastChr == '\n' && firstChar < c ) { + if ( wrapAtColumn() != -1 ) + minw = QMAX( minw, x + ww ); + if ( lastBreak < 0 ) { + 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, 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 ( parag->isNewLinesAllowed() && c->c == '\t' ) { + int nx = parag->nextTab( i, x - left ) + left; + if ( nx < x ) + ww = w - x; + else + ww = nx - x; + } + curLeft = x; + y += h; + tmph = c->height() + linespace; + 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; + } else { + DO_FLOW( lineStart ); + i = lastBreak; + lineStart = formatLine( parag, string, lineStart, firstChar, parag->at( lastBreak ), align, w - string->at( i ).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 ( parag->isNewLinesAllowed() && c->c == '\t' ) { + int nx = parag->nextTab( i, x - left ) + left; + if ( nx < x ) + ww = w - x; + else + ww = nx - x; + } + curLeft = x; + y += h; + tmph = c->height() + linespace; + 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; + tminw = marg; + continue; + } + } else if ( lineStart && ( isBreakable( string, i ) || parag->isNewLinesAllowed() && c->c == '\n' ) ) { + if ( len <= 2 || i < len - 1 ) { + tmpBaseLine = QMAX( tmpBaseLine, c->ascent() ); + tmph = QMAX( tmph, c->height() + linespace ); + } + 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 != ' ' ) + lastBreak = i; + } else { + tminw += ww; + int belowBaseLine = QMAX( tmph - tmpBaseLine, c->height() + linespace - c->ascent() ); + tmpBaseLine = QMAX( tmpBaseLine, c->ascent() ); + tmph = tmpBaseLine + belowBaseLine; + } + + c->x = x; + x += ww; + wused = QMAX( wused, x ); + } + + // ### 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->isAnchor() ) { + c->format()->removeRef(); + c->setFormat( string->at( len - 2 ).format() ); + c->format()->addRef(); + } + + 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 == Qt3::AlignJustify ) + align = Qt3::AlignAuto; + DO_FLOW( lineStart ); + lineStart = formatLine( parag, string, lineStart, firstChar, c, align, w - x ); + delete lineStart; + } + + minw = QMAX( minw, tminw ); + + int m = parag->bottomMargin(); + if ( parag->next() && doc && !doc->addMargins() ) + m = QMAX( m, parag->next()->topMargin() ); + parag->setFullWidth( fullWidth ); + if ( parag->next() && parag->next()->isLineBreak() ) + m = 0; + y += h + m; + + wused += rm; + if ( !wrapEnabled || wrapAtColumn() != -1 ) + minw = QMAX(minw, wused); + thisminw = minw; + thiswused = wused; + return y; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextIndent::QTextIndent() +{ +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +QTextFormatCollection::QTextFormatCollection() + : cKey( 307 ), sheet( 0 ) +{ + defFormat = new QTextFormat( QApplication::font(), + QApplication::palette().color( QPalette::Active, QColorGroup::Text ) ); + lastFormat = cres = 0; + cflags = -1; + cKey.setAutoDelete( TRUE ); + cachedFormat = 0; +} + +QTextFormatCollection::~QTextFormatCollection() +{ + delete defFormat; +} + +QTextFormat *QTextFormatCollection::format( QTextFormat *f ) +{ + if ( f->parent() == this || f == defFormat ) { +#ifdef DEBUG_COLLECTION + qDebug( "need '%s', best case!", f->key().latin1() ); +#endif + lastFormat = f; + lastFormat->addRef(); + return lastFormat; + } + + if ( f == lastFormat || ( lastFormat && f->key() == lastFormat->key() ) ) { +#ifdef DEBUG_COLLECTION + qDebug( "need '%s', good case!", f->key().latin1() ); +#endif + lastFormat->addRef(); + return lastFormat; + } + + QTextFormat *fm = cKey.find( f->key() ); + if ( fm ) { +#ifdef DEBUG_COLLECTION + qDebug( "need '%s', normal case!", f->key().latin1() ); +#endif + lastFormat = fm; + lastFormat->addRef(); + return lastFormat; + } + + if ( f->key() == defFormat->key() ) + return defFormat; + +#ifdef DEBUG_COLLECTION + qDebug( "need '%s', worst case!", f->key().latin1() ); +#endif + lastFormat = createFormat( *f ); + lastFormat->collection = this; + cKey.insert( lastFormat->key(), lastFormat ); + return lastFormat; +} + +QTextFormat *QTextFormatCollection::format( QTextFormat *of, QTextFormat *nf, int flags ) +{ + if ( cres && kof == of->key() && knf == nf->key() && cflags == flags ) { +#ifdef DEBUG_COLLECTION + qDebug( "mix of '%s' and '%s, best case!", of->key().latin1(), nf->key().latin1() ); +#endif + cres->addRef(); + return cres; + } + + cres = createFormat( *of ); + kof = of->key(); + knf = nf->key(); + cflags = flags; + if ( flags & QTextFormat::Bold ) + cres->fn.setBold( nf->fn.bold() ); + if ( flags & QTextFormat::Italic ) + cres->fn.setItalic( nf->fn.italic() ); + if ( flags & QTextFormat::Underline ) + cres->fn.setUnderline( nf->fn.underline() ); + if ( flags & QTextFormat::Family ) + cres->fn.setFamily( nf->fn.family() ); + if ( flags & QTextFormat::Size ) { + if ( of->usePixelSizes ) + cres->fn.setPixelSize( nf->fn.pixelSize() ); + else + cres->fn.setPointSize( nf->fn.pointSize() ); + } + if ( flags & QTextFormat::Color ) + cres->col = nf->col; + if ( flags & QTextFormat::Misspelled ) + cres->missp = nf->missp; + if ( flags & QTextFormat::VAlign ) + cres->ha = nf->ha; + cres->update(); + + QTextFormat *fm = cKey.find( cres->key() ); + if ( !fm ) { +#ifdef DEBUG_COLLECTION + qDebug( "mix of '%s' and '%s, worst case!", of->key().latin1(), nf->key().latin1() ); +#endif + cres->collection = this; + cKey.insert( cres->key(), cres ); + } else { +#ifdef DEBUG_COLLECTION + qDebug( "mix of '%s' and '%s, good case!", of->key().latin1(), nf->key().latin1() ); +#endif + delete cres; + cres = fm; + cres->addRef(); + } + + return cres; +} + +QTextFormat *QTextFormatCollection::format( const QFont &f, const QColor &c ) +{ + if ( cachedFormat && cfont == f && ccol == c ) { +#ifdef DEBUG_COLLECTION + qDebug( "format of font and col '%s' - best case", cachedFormat->key().latin1() ); +#endif + cachedFormat->addRef(); + return cachedFormat; + } + + QString key = QTextFormat::getKey( f, c, FALSE, QTextFormat::AlignNormal ); + cachedFormat = cKey.find( key ); + cfont = f; + ccol = c; + + if ( cachedFormat ) { +#ifdef DEBUG_COLLECTION + qDebug( "format of font and col '%s' - good case", cachedFormat->key().latin1() ); +#endif + 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() ); +#ifdef DEBUG_COLLECTION + qDebug( "format of font and col '%s' - worst case", cachedFormat->key().latin1() ); +#endif + return cachedFormat; +} + +void QTextFormatCollection::remove( QTextFormat *f ) +{ + if ( lastFormat == f ) + lastFormat = 0; + if ( cres == f ) + cres = 0; + if ( cachedFormat == f ) + cachedFormat = 0; + cKey.remove( f->key() ); +} + +void QTextFormatCollection::debug() +{ +#ifdef DEBUG_COLLECTION + qDebug( "------------ QTextFormatCollection: debug --------------- BEGIN" ); + QDictIterator<QTextFormat> it( cKey ); + for ( ; it.current(); ++it ) { + qDebug( "format '%s' (%p): refcount: %d", it.current()->key().latin1(), + it.current(), it.current()->ref ); + } + qDebug( "------------ QTextFormatCollection: debug --------------- END" ); +#endif +} + +void QTextFormatCollection::updateStyles() +{ + QDictIterator<QTextFormat> it( cKey ); + QTextFormat *f; + while ( ( f = it.current() ) ) { + ++it; + f->updateStyle(); + } + updateKeys(); +} + +void QTextFormatCollection::updateFontSizes( int base, bool usePixels ) +{ + QDictIterator<QTextFormat> it( cKey ); + QTextFormat *f; + while ( ( f = it.current() ) ) { + ++it; + f->stdSize = base; + f->usePixelSizes = usePixels; + if ( usePixels ) + f->fn.setPixelSize( f->stdSize ); + else + f->fn.setPointSize( f->stdSize ); + styleSheet()->scaleFont( f->fn, f->logicalFontSize ); + f->update(); + } + f = defFormat; + f->stdSize = base; + f->usePixelSizes = usePixels; + if ( usePixels ) + f->fn.setPixelSize( f->stdSize ); + else + f->fn.setPointSize( f->stdSize ); + styleSheet()->scaleFont( f->fn, f->logicalFontSize ); + f->update(); + updateKeys(); +} + +void QTextFormatCollection::updateFontAttributes( const QFont &f, const QFont &old ) +{ + QDictIterator<QTextFormat> it( cKey ); + QTextFormat *fm; + while ( ( fm = it.current() ) ) { + ++it; + if ( fm->fn.family() == old.family() && + fm->fn.weight() == old.weight() && + fm->fn.italic() == old.italic() && + fm->fn.underline() == old.underline() ) { + fm->fn.setFamily( f.family() ); + fm->fn.setWeight( f.weight() ); + fm->fn.setItalic( f.italic() ); + fm->fn.setUnderline( f.underline() ); + fm->update(); + } + } + fm = defFormat; + if ( fm->fn.family() == old.family() && + fm->fn.weight() == old.weight() && + fm->fn.italic() == old.italic() && + fm->fn.underline() == old.underline() ) { + fm->fn.setFamily( f.family() ); + fm->fn.setWeight( f.weight() ); + fm->fn.setItalic( f.italic() ); + fm->fn.setUnderline( f.underline() ); + fm->update(); + } + updateKeys(); +} + + +// the keys in cKey have changed, rebuild the hashtable +void QTextFormatCollection::updateKeys() +{ + if ( cKey.isEmpty() ) + return; + cKey.setAutoDelete( FALSE ); + QTextFormat** formats = new QTextFormat*[ cKey.count() + 1]; + QTextFormat **f = formats; + QDictIterator<QTextFormat> it( cKey ); + while ( ( *f = it.current() ) ) { + ++it; + ++f; + } + cKey.clear(); + for ( f = formats; *f; f++ ) + cKey.insert( (*f)->key(), *f ); + cKey.setAutoDelete( TRUE ); +} + + + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +void QTextFormat::setBold( bool b ) +{ + if ( b == fn.bold() ) + return; + fn.setBold( b ); + update(); +} + +void QTextFormat::setMisspelled( bool b ) +{ + if ( b == (bool)missp ) + return; + missp = b; + update(); +} + +void QTextFormat::setVAlign( VerticalAlignment a ) +{ + if ( a == ha ) + return; + ha = a; + update(); +} + +void QTextFormat::setItalic( bool b ) +{ + if ( b == fn.italic() ) + return; + fn.setItalic( b ); + update(); +} + +void QTextFormat::setUnderline( bool b ) +{ + if ( b == fn.underline() ) + return; + fn.setUnderline( b ); + update(); +} + +void QTextFormat::setFamily( const QString &f ) +{ + if ( f == fn.family() ) + return; + fn.setFamily( f ); + update(); +} + +void QTextFormat::setPointSize( int s ) +{ + if ( s == fn.pointSize() ) + return; + fn.setPointSize( s ); + usePixelSizes = FALSE; + update(); +} + +void QTextFormat::setFont( const QFont &f ) +{ + if ( f == fn && !k.isEmpty() ) + return; + fn = f; + update(); +} + +void QTextFormat::setColor( const QColor &c ) +{ + if ( c == col ) + return; + col = c; + update(); +} + +static int makeLogicFontSize( int s ) +{ + int defSize = QApplication::font().pointSize(); + if ( s < defSize - 4 ) + return 1; + if ( s < defSize ) + return 2; + if ( s < defSize + 4 ) + return 3; + if ( s < defSize + 8 ) + return 4; + if ( s < defSize + 12 ) + return 5; + if (s < defSize + 16 ) + return 6; + return 7; +} + +static QTextFormat *defaultFormat = 0; + +QString QTextFormat::makeFormatChangeTags( QTextFormat *f, const QString& oldAnchorHref, const QString& anchorHref ) const +{ + if ( !defaultFormat ) // #### wrong, use the document's default format instead + defaultFormat = new QTextFormat( QApplication::font(), + QApplication::palette().color( QPalette::Active, QColorGroup::Text ) ); + + QString tag; + if ( f ) { + if ( f->font() != defaultFormat->font() ) { + if ( f->font().family() != defaultFormat->font().family() + || f->font().pointSize() != defaultFormat->font().pointSize() + || f->color().rgb() != defaultFormat->color().rgb() ) + tag += "</font>"; + if ( f->font().underline() && f->font().underline() != defaultFormat->font().underline() ) + tag += "</u>"; + if ( f->font().italic() && f->font().italic() != defaultFormat->font().italic() ) + tag += "</i>"; + if ( f->font().bold() && f->font().bold() != defaultFormat->font().bold() ) + tag += "</b>"; + } + if ( !oldAnchorHref.isEmpty() ) + tag += "</a>"; + } + + if ( !anchorHref.isEmpty() ) + tag += "<a href=\"" + anchorHref + "\">"; + + if ( font() != defaultFormat->font() ) { + if ( font().bold() && font().bold() != defaultFormat->font().bold() ) + tag += "<b>"; + if ( font().italic() && font().italic() != defaultFormat->font().italic() ) + tag += "<i>"; + if ( font().underline() && font().underline() != defaultFormat->font().underline() ) + tag += "<u>"; + } + if ( font() != defaultFormat->font() + || color().rgb() != defaultFormat->color().rgb() ) { + QString f; + if ( font().family() != defaultFormat->font().family() ) + f +=" face=\"" + fn.family() + "\""; + if ( font().pointSize() != defaultFormat->font().pointSize() ) { + f +=" size=\"" + QString::number( makeLogicFontSize( fn.pointSize() ) ) + "\""; + f +=" style=\"font-size:" + QString::number( fn.pointSize() ) + "pt\""; + } + if ( color().rgb() != defaultFormat->color().rgb() ) + f +=" color=\"" + col.name() + "\""; + if ( !f.isEmpty() ) + tag += "<font" + f + ">"; + } + + return tag; +} + +QString QTextFormat::makeFormatEndTags( const QString& anchorHref ) const +{ + if ( !defaultFormat ) + defaultFormat = new QTextFormat( QApplication::font(), + QApplication::palette().color( QPalette::Active, QColorGroup::Text ) ); + + QString tag; + if ( font() != defaultFormat->font() ) { + if ( font().family() != defaultFormat->font().family() + || font().pointSize() != defaultFormat->font().pointSize() + || color().rgb() != defaultFormat->color().rgb() ) + tag += "</font>"; + if ( font().underline() && font().underline() != defaultFormat->font().underline() ) + tag += "</u>"; + if ( font().italic() && font().italic() != defaultFormat->font().italic() ) + tag += "</i>"; + if ( font().bold() && font().bold() != defaultFormat->font().bold() ) + tag += "</b>"; + } + if ( !anchorHref.isEmpty() ) + tag += "</a>"; + return tag; +} + +QTextFormat QTextFormat::makeTextFormat( const QStyleSheetItem *style, const QMap<QString,QString>& attr ) const +{ + QTextFormat format(*this); + if ( style ) { + format.style = style->name(); + if ( style->name() == "font") { + if ( attr.contains("color") ) { + QString s = attr["color"]; + if ( !s.isEmpty() ) { + format.col.setNamedColor( s ); + format.linkColor = FALSE; + } + } + if ( attr.contains("size") ) { + QString a = attr["size"]; + int n = a.toInt(); + if ( a[0] == '+' || a[0] == '-' ) + n += format.logicalFontSize; + format.logicalFontSize = n; + if ( format.usePixelSizes ) + format.fn.setPixelSize( format.stdSize ); + else + format.fn.setPointSize( format.stdSize ); + style->styleSheet()->scaleFont( format.fn, format.logicalFontSize ); + } + if ( attr.contains("style" ) ) { + QString a = attr["style"]; + if ( a.startsWith( "font-size:" ) ) { + QString s = a.mid( a.find( ':' ) + 1 ); + int n = s.left( s.length() - 2 ).toInt(); + format.logicalFontSize = 0; + if ( format.usePixelSizes ) + format.fn.setPixelSize( n ); + else + format.fn.setPointSize( n ); + } + } + if ( attr.contains("face") ) { + QString a = attr["face"]; + if ( a.contains(',') ) + a = a.left( a.find(',') ); + format.fn.setFamily( a ); + } + } else { + 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 QStyleSheetItem::VAlignBaseline: + format.setVAlign( QTextFormat::AlignNormal ); + break; + case QStyleSheetItem::VAlignSuper: + format.setVAlign( QTextFormat::AlignSuperScript ); + break; + case QStyleSheetItem::VAlignSub: + format.setVAlign( QTextFormat::AlignSubScript ); + break; + } + + if ( style->fontWeight() != QStyleSheetItem::Undefined ) + format.fn.setWeight( style->fontWeight() ); + if ( style->fontSize() != QStyleSheetItem::Undefined ) { + format.fn.setPointSize( style->fontSize() ); + } else if ( style->logicalFontSize() != QStyleSheetItem::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() ); + } + } + + format.update(); + return format; +} + +struct QPixmapInt +{ + QPixmapInt() : ref( 0 ) {} + QPixmap pm; + int ref; +}; + +static QMap<QString, QPixmapInt> *pixmap_map = 0; + +QTextImage::QTextImage( QTextDocument *p, const QMap<QString, QString> &attr, const QString& context, + QMimeSourceFactory &factory ) + : QTextCustomItem( p ) +{ +#if defined(PARSER_DEBUG) + qDebug( debug_indent + "new QTextImage (pappi: %p)", p ); +#endif + + width = height = 0; + if ( attr.contains("width") ) + width = attr["width"].toInt(); + if ( attr.contains("height") ) + height = attr["height"].toInt(); + + reg = 0; + QString imageName = attr["src"]; + + if (!imageName) + imageName = attr["source"]; + +#if defined(PARSER_DEBUG) + qDebug( debug_indent + " .." + imageName ); +#endif + + if ( !imageName.isEmpty() ) { + imgId = QString( "%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 ) { + qWarning("QTextImage: no mimesource for %s", imageName.latin1() ); + } + else { + if ( !QImageDrag::decode( m, img ) ) { + qWarning("QTextImage: 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.mask() ) { + QRegion mask( *pm.mask() ); + QRegion all( 0, 0, pm.width(), pm.height() ); + reg = new QRegion( all.subtract( mask ) ); + } + } + + if ( pm.isNull() && (width*height)==0 ) + width = height = 50; + + place = PlaceInline; + if ( attr["align"] == "left" ) + place = PlaceLeft; + else if ( attr["align"] == "right" ) + place = PlaceRight; + + tmpwidth = width; + tmpheight = height; + + attributes = attr; +} + +QTextImage::~QTextImage() +{ + 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; + } + } + } +} + +QString QTextImage::richText() const +{ + QString s; + s += "<img "; + QMap<QString, QString>::ConstIterator it = attributes.begin(); + for ( ; it != attributes.end(); ++it ) + s += it.key() + "=" + *it + " "; + s += ">"; + return s; +} + +void QTextImage::adjustToPainter( QPainter* p ) +{ + width = scale( tmpwidth, p ); + height = scale( tmpheight, p ); +} + +#if !defined(Q_WS_X11) +#include <qbitmap.h> +#include "qcleanuphandler.h" +static QPixmap *qrt_selection = 0; +static QSingleCleanupHandler<QPixmap> qrt_cleanup_pixmap; +static void qrt_createSelectionPixmap( const QColorGroup &cg ) +{ + 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( cg.highlight() ); +} +#endif + +void QTextImage::draw( QPainter* p, int x, int y, int cx, int cy, int cw, int ch, const QColorGroup& cg, bool selected ) +{ + if ( placement() != PlaceInline ) { + x = xpos; + y = ypos; + } + + if ( pm.isNull() ) { + p->fillRect( x , y, width, height, cg.dark() ); + return; + } + + if ( is_printer( p ) ) { + p->drawPixmap( x, y, 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( cg.highlight(), QBrush::Dense4Pattern) ); +#else // in WIN32 Dense4Pattern doesn't work correctly (transparency problem), so work around it + if ( !qrt_selection ) + qrt_createSelectionPixmap( cg ); + p->drawTiledPixmap( x, y, pm.width(), pm.height(), *qrt_selection ); +#endif + } +} + +void QTextHorizontalLine::adjustToPainter( QPainter* p ) +{ + height = scale( tmpheight, p ); +} + + +QTextHorizontalLine::QTextHorizontalLine( QTextDocument *p, const QMap<QString, QString> &attr, + const QString &, + QMimeSourceFactory & ) + : QTextCustomItem( p ) +{ + height = tmpheight = 8; + if ( attr.find( "color" ) != attr.end() ) + color = QColor( *attr.find( "color" ) ); +} + +QTextHorizontalLine::~QTextHorizontalLine() +{ +} + +QString QTextHorizontalLine::richText() const +{ + return "<hr>"; +} + +void QTextHorizontalLine::draw( QPainter* p, int x, int y, int , int , int , int , const QColorGroup& cg, bool selected ) +{ + QRect r( x, y, width, height); + if ( is_printer( p ) ) { + QPen oldPen = p->pen(); + if ( !color.isValid() ) + p->setPen( QPen( cg.text(), height/8 ) ); + else + p->setPen( QPen( color, height/8 ) ); + p->drawLine( r.left()-1, y + height / 2, r.right() + 1, y + height / 2 ); + p->setPen( oldPen ); + } else { + QColorGroup g( cg ); + if ( color.isValid() ) + g.setColor( QColorGroup::Dark, color ); + if ( selected ) + p->fillRect( r.left(), y, r.right(), y + height, g.highlight() ); + qDrawShadeLine( p, r.left() - 1, y + height / 2, r.right() + 1, y + height / 2, g, TRUE, height / 8 ); + } +} + + +/*****************************************************************/ +// Small set of utility functions to make the parser a bit simpler +// + +bool QTextDocument::hasPrefix(const QChar* doc, int length, int pos, QChar c) +{ + if ( pos >= length ) + return FALSE; + return doc[ pos ].lower() == c.lower(); +} + +bool QTextDocument::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 ].lower() != s[ i ].lower() ) + return FALSE; + } + return TRUE; +} + +static bool qt_is_cell_in_use( QPtrList<QTextTableCell>& cells, int row, int col ) +{ + for ( QTextTableCell* c = cells.first(); c; c = cells.next() ) { + if ( row >= c->row() && row < c->row() + c->rowspan() + && col >= c->column() && col < c->column() + c->colspan() ) + return TRUE; + } + return FALSE; +} + +QTextCustomItem* QTextDocument::parseTable( const QMap<QString, QString> &attr, const QTextFormat &fmt, + const QChar* doc, int length, int& pos, QTextParag *curpar ) +{ + + QTextTable* table = new QTextTable( this, attr ); + int row = -1; + int col = -1; + + QString rowbgcolor; + QString rowalign; + QString tablebgcolor = attr["bgcolor"]; + + QPtrList<QTextTableCell> multicells; + + QString tagname; + (void) eatSpace(doc, length, pos); + while ( pos < length) { + if (hasPrefix(doc, length, pos, QChar('<')) ){ + if (hasPrefix(doc, length, pos+1, QChar('/'))) { + tagname = parseCloseTag( doc, length, pos ); + if ( tagname == "table" ) { +#if defined(PARSER_DEBUG) + debug_indent.remove( debug_indent.length() - 3, 2 ); +#endif + return table; + } + } else { + QMap<QString, QString> attr2; + bool emptyTag = FALSE; + tagname = parseOpenTag( doc, length, pos, attr2, emptyTag ); + if ( tagname == "tr" ) { + rowbgcolor = attr2["bgcolor"]; + rowalign = attr2["align"]; + row++; + col = -1; + } + else if ( tagname == "td" || tagname == "th" ) { + col++; + while ( qt_is_cell_in_use( multicells, row, col ) ) { + col++; + } + + if ( row >= 0 && col >= 0 ) { + const QStyleSheetItem* s = sheet_->item(tagname); + if ( !attr2.contains("bgcolor") ) { + if (!rowbgcolor.isEmpty() ) + attr2["bgcolor"] = rowbgcolor; + else if (!tablebgcolor.isEmpty() ) + attr2["bgcolor"] = tablebgcolor; + } + if ( !attr2.contains("align") ) { + if (!rowalign.isEmpty() ) + attr2["align"] = rowalign; + } + + // extract the cell contents + int end = pos; + while ( end < length + && !hasPrefix( doc, length, end, "</td") + && !hasPrefix( doc, length, end, "<td") + && !hasPrefix( doc, length, end, "</th") + && !hasPrefix( doc, length, end, "<th") + && !hasPrefix( doc, length, end, "<td") + && !hasPrefix( doc, length, end, "</tr") + && !hasPrefix( doc, length, end, "<tr") + && !hasPrefix( doc, length, end, "</table") ) { + if ( hasPrefix( doc, length, end, "<table" ) ) { // nested table + int nested = 1; + ++end; + while ( end < length && nested != 0 ) { + if ( hasPrefix( doc, length, end, "</table" ) ) + nested--; + if ( hasPrefix( doc, length, end, "<table" ) ) + nested++; + end++; + } + } + end++; + } + QTextTableCell* cell = new QTextTableCell( table, row, col, + attr2, s, fmt.makeTextFormat( s, attr2 ), + contxt, *factory_, sheet_, + QString( doc, length).mid( pos, end - pos ) ); + cell->richText()->parParag = curpar; + if ( cell->colspan() > 1 || cell->rowspan() > 1 ) + multicells.append( cell ); + col += cell->colspan()-1; + pos = end; + } + } + } + + } else { + ++pos; + } + } +#if defined(PARSER_DEBUG) + debug_indent.remove( debug_indent.length() - 3, 2 ); +#endif + return table; +} + +bool QTextDocument::eatSpace(const QChar* doc, int length, int& pos, bool includeNbsp ) +{ + int old_pos = pos; + while (pos < length && doc[pos].isSpace() && ( includeNbsp || (doc[pos] != QChar::nbsp ) ) ) + pos++; + return old_pos < pos; +} + +bool QTextDocument::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 }, + { "curren", 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<QCString, QChar> *html_map = 0; +static void qt_cleanup_html_map() +{ + delete html_map; + html_map = 0; +} + +static QMap<QCString, QChar> *htmlMap() +{ + if ( !html_map ) { + html_map = new QMap<QCString, QChar>; + qAddPostRoutine( qt_cleanup_html_map ); + + const Entity *ent = entitylist; + while( ent->code ) { + html_map->insert( ent->name, QChar(ent->code) ); + ent++; + } + } + return html_map; +} + +QChar QTextDocument::parseHTMLSpecialChar(const QChar* doc, int length, int& pos) +{ + QCString s; + pos++; + int recoverpos = pos; + while ( pos < length && doc[pos] != ';' && !doc[pos].isSpace() && pos < recoverpos + 6) { + s += doc[pos]; + pos++; + } + if (doc[pos] != ';' && !doc[pos].isSpace() ) { + pos = recoverpos; + return '&'; + } + pos++; + + if ( s.length() > 1 && s[0] == '#') { + int num = s.mid(1).toInt(); + if ( num == 151 ) // ### hack for designer manual + return '-'; + return num; + } + + QMap<QCString, QChar>::Iterator it = htmlMap()->find(s); + if ( it != htmlMap()->end() ) { + return *it; + } + + pos = recoverpos; + return '&'; +} + +QString QTextDocument::parseWord(const QChar* doc, int length, int& pos, bool lower) +{ + QString s; + + if (doc[pos] == '"') { + pos++; + while ( pos < length && doc[pos] != '"' ) { + s += doc[pos]; + pos++; + } + eat(doc, length, pos, '"'); + } else { + static QString term = QString::fromLatin1("/>"); + while( pos < length && + (doc[pos] != '>' && !hasPrefix( doc, length, pos, term)) + && doc[pos] != '<' + && doc[pos] != '=' + && !doc[pos].isSpace()) + { + if ( doc[pos] == '&') + s += parseHTMLSpecialChar( doc, length, pos ); + else { + s += doc[pos]; + pos++; + } + } + if (lower) + s = s.lower(); + } + return s; +} + +QChar QTextDocument::parseChar(const QChar* doc, int length, int& pos, QStyleSheetItem::WhiteSpaceMode wsm ) +{ + if ( pos >= length ) + return QChar::null; + + QChar c = doc[pos++]; + + if (c == '<' ) + return QChar::null; + + if ( c.isSpace() && c != QChar::nbsp ) { + if ( wsm == QStyleSheetItem::WhiteSpacePre ) { + if ( c == ' ' ) + return QChar::nbsp; + else + return c; + } else if ( wsm == QStyleSheetItem_WhiteSpaceNoCompression ) { + return c; + } else if ( wsm == QStyleSheetItem_WhiteSpaceNormalWithNewlines ) { + if ( c == '\n' ) + return c; + while ( pos< length && + doc[pos].isSpace() && doc[pos] != QChar::nbsp && doc[pos] != '\n' ) + pos++; + return ' '; + } else { // non-pre mode: collapse whitespace except nbsp + while ( pos< length && + doc[pos].isSpace() && doc[pos] != QChar::nbsp ) + pos++; + if ( wsm == QStyleSheetItem::WhiteSpaceNoWrap ) + return QChar::nbsp; + else + return ' '; + } + } + else if ( c == '&' ) + return parseHTMLSpecialChar( doc, length, --pos ); + else + return c; +} + +QString QTextDocument::parseOpenTag(const QChar* doc, int length, int& pos, + QMap<QString, QString> &attr, bool& emptyTag) +{ + emptyTag = FALSE; + pos++; + if ( hasPrefix(doc, length, pos, '!') ) { + if ( hasPrefix( doc, length, pos+1, "--")) { + 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::null; + } + else { + // eat strange internal tags + while ( !hasPrefix(doc, length, pos, '>') && pos < length ) + pos++; + if ( hasPrefix(doc, length, pos, '>') ) { + pos++; + eatSpace(doc, length, pos, TRUE); + } + return QString::null; + } + } + + 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] != '>' && ! (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] != '>' ) + pos++; + break; + } + QString value; + if (hasPrefix(doc, length, pos, '=') ){ + pos++; + eatSpace(doc, length, pos); + value = parseWord(doc, length, pos, FALSE); + } + else + value = s_TRUE; + attr.insert(key.lower(), value ); + eatSpace(doc, length, pos, TRUE); + } + + if (emptyTag) { + eat(doc, length, pos, '/'); + eat(doc, length, pos, '>'); + } + else + eat(doc, length, pos, '>'); + + return tag; +} + +QString QTextDocument::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, '>'); + return tag; +} + +QTextFlow::QTextFlow() +{ + w = pagesize = 0; + leftItems.setAutoDelete( FALSE ); + rightItems.setAutoDelete( FALSE ); +} + +QTextFlow::~QTextFlow() +{ +} + +void QTextFlow::clear() +{ + leftItems.clear(); + rightItems.clear(); +} + +void QTextFlow::setWidth( int width ) +{ + w = width; +} + +int QTextFlow::adjustLMargin( int yp, int, int margin, int space ) +{ + for ( QTextCustomItem* item = leftItems.first(); item; item = leftItems.next() ) { + if ( item->ypos == -1 ) + continue; + if ( yp >= item->ypos && yp < item->ypos + item->height ) + margin = QMAX( margin, item->xpos + item->width + space ); + } + return margin; +} + +int QTextFlow::adjustRMargin( int yp, int, int margin, int space ) +{ + for ( QTextCustomItem* item = rightItems.first(); item; item = rightItems.next() ) { + if ( item->ypos == -1 ) + continue; + if ( yp >= item->ypos && yp < item->ypos + item->height ) + margin = QMAX( margin, w - item->xpos - space ); + } + return margin; +} + + +int QTextFlow::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; +} + +void QTextFlow::unregisterFloatingItem( QTextCustomItem* item ) +{ + leftItems.removeRef( item ); + rightItems.removeRef( item ); +} + +void QTextFlow::registerFloatingItem( QTextCustomItem* item ) +{ + if ( item->placement() == QTextCustomItem::PlaceRight ) { + if ( !rightItems.contains( item ) ) + rightItems.append( item ); + } else if ( item->placement() == QTextCustomItem::PlaceLeft && + !leftItems.contains( item ) ) { + leftItems.append( item ); + } +} + +QRect QTextFlow::boundingRect() const +{ + QRect br; + QPtrListIterator<QTextCustomItem> l( leftItems ); + while( l.current() ) { + br = br.unite( l.current()->geometry() ); + ++l; + } + QPtrListIterator<QTextCustomItem> r( rightItems ); + while( r.current() ) { + br = br.unite( r.current()->geometry() ); + ++r; + } + return br; +} + + +void QTextFlow::drawFloatingItems( QPainter* p, int cx, int cy, int cw, int ch, const QColorGroup& cg, bool selected ) +{ + QTextCustomItem *item; + for ( item = leftItems.first(); item; item = leftItems.next() ) { + if ( item->xpos == -1 || item->ypos == -1 ) + continue; + item->draw( p, item->xpos, item->ypos, cx, cy, cw, ch, cg, selected ); + } + + for ( item = rightItems.first(); item; item = rightItems.next() ) { + if ( item->xpos == -1 || item->ypos == -1 ) + continue; + item->draw( p, item->xpos, item->ypos, cx, cy, cw, ch, cg, selected ); + } +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +void QTextCustomItem::pageBreak( int /*y*/ , QTextFlow* /*flow*/ ) +{ +} + +QTextTable::QTextTable( QTextDocument *p, const QMap<QString, QString> & attr ) + : QTextCustomItem( p ) +{ + cells.setAutoDelete( FALSE ); +#if defined(PARSER_DEBUG) + debug_indent += "\t"; + qDebug( debug_indent + "new QTextTable (%p)", this ); + debug_indent += "\t"; +#endif + cellspacing = 2; + if ( attr.contains("cellspacing") ) + cellspacing = attr["cellspacing"].toInt(); + cellpadding = 1; + if ( attr.contains("cellpadding") ) + cellpadding = attr["cellpadding"].toInt(); + border = innerborder = 0; + if ( attr.contains("border" ) ) { + QString s( attr["border"] ); + if ( s == "TRUE" ) + border = 1; + else + border = attr["border"].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; + if ( attr.contains("width") ) { + bool b; + QString s( attr["width"] ); + int w = s.toInt( &b ); + if ( b ) { + fixwidth = w; + } else { + s = s.stripWhiteSpace(); + if ( s.length() > 1 && s[ (int)s.length()-1 ] == '%' ) + stretch = s.left( s.length()-1).toInt(); + } + } + + place = PlaceInline; + if ( attr["align"] == "left" ) + place = PlaceLeft; + else if ( attr["align"] == "right" ) + place = PlaceRight; + cachewidth = 0; + attributes = attr; + pageBreakFor = -1; +} + +QTextTable::~QTextTable() +{ + delete layout; +} + +QString QTextTable::richText() const +{ + QString s; + s = "<table "; + QMap<QString, QString>::ConstIterator it = attributes.begin(); + for ( ; it != attributes.end(); ++it ) + s += it.key() + "=" + *it + " "; + s += ">\n"; + + int lastRow = -1; + bool needEnd = FALSE; + QPtrListIterator<QTextTableCell> it2( cells ); + while ( it2.current() ) { + QTextTableCell *cell = it2.current(); + ++it2; + if ( lastRow != cell->row() ) { + if ( lastRow != -1 ) + s += "</tr>\n"; + s += "<tr>"; + lastRow = cell->row(); + needEnd = TRUE; + } + s += "<td "; + it = cell->attributes.begin(); + for ( ; it != cell->attributes.end(); ++it ) + s += it.key() + "=" + *it + " "; + s += ">"; + s += cell->richText()->richText(); + s += "</td>"; + } + if ( needEnd ) + s += "</tr>\n"; + s += "</table>\n"; + return s; +} + +void QTextTable::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 ); + width = 0; + cachewidth = 0; + for ( QTextTableCell* cell = cells.first(); cell; cell = cells.next() ) + cell->adjustToPainter( p ); +} + +void QTextTable::adjustCells( int y , int shift ) +{ + QPtrListIterator<QTextTableCell> it( cells ); + QTextTableCell* cell; + bool enlarge = FALSE; + while ( ( cell = it.current() ) ) { + ++it; + 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 QTextTable::pageBreak( int yt, QTextFlow* 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; + QPtrListIterator<QTextTableCell> it( cells ); + QTextTableCell* cell; + while ( ( cell = it.current() ) ) { + ++it; + 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 QTextTable::draw(QPainter* p, int x, int y, int cx, int cy, int cw, int ch, const QColorGroup& cg, bool selected ) +{ + if ( placement() != PlaceInline ) { + x = xpos; + y = ypos; + } + + for (QTextTableCell* cell = cells.first(); cell; cell = cells.next() ) { + 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, cg, 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.setLeft( r2.left() + innerborder/2 ); + r2.setTop( r2.top() + innerborder/2 ); + r2.setRight( r2.right() - innerborder/2 ); + r2.setBottom( r2.bottom() - innerborder/2 ); + p->setPen( QPen( cg.text(), 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(), cg.button() ); + p->fillRect( r.right(), r.top(), s+1, r.height(), cg.button() ); + p->fillRect( r.left()-s, r.top()-s, r.width()+2*s, s, cg.button() ); + p->fillRect( r.left()-s, r.bottom(), r.width()+2*s, s, cg.button() ); + } + qDrawShadePanel( p, r, cg, TRUE, innerborder ); + } + } + } + } + if ( border ) { + QRect r ( x, y, width, height ); + if ( is_printer( p ) ) { + QRect r2 = r; + r2.setLeft( r2.left() + border/2 ); + r2.setTop( r2.top() + border/2 ); + r2.setRight( r2.right() - border/2 ); + r2.setBottom( r2.bottom() - border/2 ); + QPen oldPen = p->pen(); + p->setPen( QPen( cg.text(), 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(), cg.button() ); + p->fillRect( r.right()-s, r.top(), s, r.height(), cg.button() ); + p->fillRect( r.left(), r.top(), r.width(), s, cg.button() ); + p->fillRect( r.left(), r.bottom()-s, r.width(), s, cg.button() ); + } + qDrawShadePanel( p, r, cg, FALSE, border ); + } + } + +#if defined(DEBUG_TABLE_RENDERING) + p->save(); + p->setPen( Qt::red ); + p->drawRect( x, y, width, height ); + p->restore(); +#endif +} + +int QTextTable::minimumWidth() const +{ + return (layout ? layout->minimumSize().width() : 0) + 2 * outerborder; +} + +void QTextTable::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 QTextTable::format( int w ) +{ + for ( int i = 0; i < (int)cells.count(); ++i ) { + QTextTableCell *cell = cells.at( i ); + QRect r = cell->geometry(); + r.setWidth( w - 2*outerborder ); + cell->setGeometry( r ); + } +} + +void QTextTable::addCell( QTextTableCell* cell ) +{ + cells.append( cell ); + layout->addMultiCell( cell, cell->row(), cell->row() + cell->rowspan()-1, + cell->column(), cell->column() + cell->colspan()-1 ); +} + +bool QTextTable::enter( QTextCursor *c, QTextDocument *&doc, QTextParag *¶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 QTextTable::enterAt( QTextCursor *c, QTextDocument *&doc, QTextParag *¶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 ) { + QTextTableCell *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; + } + + QTextTableCell *cell = cells.at( *currCell.find( c ) ); + if ( !cell ) + return FALSE; + doc = cell->richText(); + parag = doc->firstParag(); + idx = 0; + ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x(); + oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder; + return TRUE; +} + +bool QTextTable::next( QTextCursor *c, QTextDocument *&doc, QTextParag *¶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 ); + QTextCustomItem::next( c, doc, parag, idx, ox, oy ); + QTextTableCell *cell = cells.first(); + if ( !cell ) + return FALSE; + doc = cell->richText(); + idx = -1; + return TRUE; + } + + if ( currCell.find( c ) == currCell.end() ) + return FALSE; + QTextTableCell *cell = cells.at( *currCell.find( c ) ); + if ( !cell ) + return FALSE; + doc = cell->richText(); + parag = doc->firstParag(); + idx = 0; + ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x(); + oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder; + return TRUE; +} + +bool QTextTable::prev( QTextCursor *c, QTextDocument *&doc, QTextParag *¶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 ); + QTextCustomItem::prev( c, doc, parag, idx, ox, oy ); + QTextTableCell *cell = cells.first(); + if ( !cell ) + return FALSE; + doc = cell->richText(); + idx = -1; + return TRUE; + } + + if ( currCell.find( c ) == currCell.end() ) + return FALSE; + QTextTableCell *cell = cells.at( *currCell.find( c ) ); + if ( !cell ) + return FALSE; + doc = cell->richText(); + parag = doc->firstParag(); + idx = parag->length() - 1; + ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x(); + oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder; + return TRUE; +} + +bool QTextTable::down( QTextCursor *c, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy ) +{ + if ( currCell.find( c ) == currCell.end() ) + return FALSE; + QTextTableCell *cell = cells.at( *currCell.find( c ) ); + if ( cell->row_ == layout->numRows() - 1 ) { + currCell.insert( c, 0 ); + QTextCustomItem::down( c, doc, parag, idx, ox, oy ); + QTextTableCell *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->firstParag(); + idx = 0; + ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x(); + oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder; + return TRUE; +} + +bool QTextTable::up( QTextCursor *c, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy ) +{ + if ( currCell.find( c ) == currCell.end() ) + return FALSE; + QTextTableCell *cell = cells.at( *currCell.find( c ) ); + if ( cell->row_ == 0 ) { + currCell.insert( c, 0 ); + QTextCustomItem::up( c, doc, parag, idx, ox, oy ); + QTextTableCell *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->lastParag(); + idx = parag->length() - 1; + ox += cell->geometry().x() + cell->horizontalAlignmentOffset() + outerborder + parent->x(); + oy += cell->geometry().y() + cell->verticalAlignmentOffset() + outerborder; + return TRUE; +} + +QTextTableCell::QTextTableCell( QTextTable* table, + int row, int column, + const QMap<QString, QString> &attr, + const QStyleSheetItem* /*style*/, // ### use them + const QTextFormat& /*fmt*/, const QString& context, + QMimeSourceFactory &factory, QStyleSheet *sheet, + const QString& doc) +{ +#if defined(PARSER_DEBUG) + qDebug( debug_indent + "new QTextTableCell1 (pappi: %p)", table ); + qDebug( debug_indent + doc ); +#endif + cached_width = -1; + cached_sizehint = -1; + + maxw = QWIDGETSIZE_MAX; + minw = 0; + + parent = table; + row_ = row; + col_ = column; + stretch_ = 0; + richtext = new QTextDocument( table->parent ); + richtext->setTableCell( this ); + QString a = *attr.find( "align" ); + if ( !a.isEmpty() ) { + a = a.lower(); + if ( a == "left" ) + richtext->setAlignment( Qt::AlignLeft ); + else if ( a == "center" ) + richtext->setAlignment( Qt::AlignHCenter ); + else if ( a == "right" ) + richtext->setAlignment( Qt::AlignRight ); + } + align = 0; + QString va = *attr.find( "valign" ); + if ( !va.isEmpty() ) { + va = va.lower(); + if ( va == "center" ) + align |= Qt::AlignVCenter; + else if ( va == "bottom" ) + align |= Qt::AlignBottom; + } + richtext->setFormatter( table->parent->formatter() ); + richtext->setUseFormatCollection( table->parent->useFormatCollection() ); + richtext->setMimeSourceFactory( &factory ); + richtext->setStyleSheet( sheet ); + richtext->setDefaultFont( table->parent->formatCollection()->defaultFormat()->font() ); + richtext->setRichText( doc, context ); + rowspan_ = 1; + colspan_ = 1; + if ( attr.contains("colspan") ) + colspan_ = attr["colspan"].toInt(); + if ( attr.contains("rowspan") ) + rowspan_ = attr["rowspan"].toInt(); + + background = 0; + if ( attr.contains("bgcolor") ) { + background = new QBrush(QColor( attr["bgcolor"] )); + } + + + hasFixedWidth = FALSE; + if ( attr.contains("width") ) { + bool b; + QString s( attr["width"] ); + int w = s.toInt( &b ); + if ( b ) { + maxw = w; + minw = maxw; + hasFixedWidth = TRUE; + } else { + s = s.stripWhiteSpace(); + if ( s.length() > 1 && s[ (int)s.length()-1 ] == '%' ) + stretch_ = s.left( s.length()-1).toInt(); + } + } + + attributes = attr; + + parent->addCell( this ); +} + +QTextTableCell::QTextTableCell( QTextTable* table, int row, int column ) +{ +#if defined(PARSER_DEBUG) + qDebug( debug_indent + "new QTextTableCell2( pappi: %p", table ); +#endif + maxw = QWIDGETSIZE_MAX; + minw = 0; + cached_width = -1; + cached_sizehint = -1; + + parent = table; + row_ = row; + col_ = column; + stretch_ = 0; + richtext = new QTextDocument( table->parent ); + richtext->setTableCell( this ); + richtext->setFormatter( table->parent->formatter() ); + richtext->setUseFormatCollection( table->parent->useFormatCollection() ); + richtext->setDefaultFont( table->parent->formatCollection()->defaultFormat()->font() ); + richtext->setRichText( "<html></html>", QString::null ); + rowspan_ = 1; + colspan_ = 1; + background = 0; + hasFixedWidth = FALSE; + parent->addCell( this ); +} + + +QTextTableCell::~QTextTableCell() +{ + delete background; + background = 0; + delete richtext; + richtext = 0; +} + +QSize QTextTableCell::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 QTextTableCell::minimumSize() const +{ + int extra = 2 * ( parent->innerborder + parent->cellpadding + border_tolerance); + return QSize( QMAX( richtext->minimumWidth() + extra, minw), 0 ); +} + +QSize QTextTableCell::maximumSize() const +{ + return QSize( QWIDGETSIZE_MAX, QWIDGETSIZE_MAX ); +} + +QSizePolicy::ExpandData QTextTableCell::expanding() const +{ + return QSizePolicy::BothDirections; +} + +bool QTextTableCell::isEmpty() const +{ + return FALSE; +} +void QTextTableCell::setGeometry( const QRect& r ) +{ + int extra = 2 * ( parent->innerborder + parent->cellpadding ); + if ( r.width() != cached_width ) + richtext->doLayout( QTextFormat::painter(), r.width() - extra ); + cached_width = r.width(); + geom = r; +} + +QRect QTextTableCell::geometry() const +{ + return geom; +} + +bool QTextTableCell::hasHeightForWidth() const +{ + return TRUE; +} + +int QTextTableCell::heightForWidth( int w ) const +{ + int extra = 2 * ( parent->innerborder + parent->cellpadding ); + w = QMAX( minw, w ); + + if ( cached_width != w ) { + QTextTableCell* that = (QTextTableCell*) this; + that->richtext->doLayout( QTextFormat::painter(), w - extra ); + that->cached_width = w; + } + return richtext->height() + extra; +} + +void QTextTableCell::adjustToPainter( QPainter* p ) +{ + QTextParag *parag = richtext->firstParag(); + while ( parag ) { + parag->adjustToPainter( p ); + parag = parag->next(); + } +} + +int QTextTableCell::horizontalAlignmentOffset() const +{ + return parent->cellpadding; +} + +int QTextTableCell::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 QTextTableCell::draw( QPainter* p, int x, int y, int cx, int cy, int cw, int ch, const QColorGroup& cg, bool ) +{ + if ( cached_width != geom.width() ) { + int extra = 2 * ( parent->innerborder + parent->cellpadding ); + richtext->doLayout( p, geom.width() - extra ); + cached_width = geom.width(); + } + QColorGroup g( cg ); + if ( background ) + g.setBrush( QColorGroup::Base, *background ); + else if ( richtext->paper() ) + g.setBrush( QColorGroup::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; + QTextCursor *c = 0; + if ( richtext->parent()->tmpCursor ) + c = richtext->parent()->tmpCursor; + if ( cx >= 0 && cy >= 0 ) + richtext->draw( p, cx - ( x + horizontalAlignmentOffset() + geom.x() ), + cy - ( y + geom.y() + verticalAlignmentOffset() ), + cw, ch, g, FALSE, (c != 0), c ); + else + richtext->draw( p, -1, -1, -1, -1, g, FALSE, (c != 0), c ); + + p->restore(); +} diff --git a/noncore/apps/opie-write/qrichtext_p.cpp b/noncore/apps/opie-write/qrichtext_p.cpp new file mode 100644 index 0000000..fb20730 --- a/dev/null +++ b/noncore/apps/opie-write/qrichtext_p.cpp @@ -0,0 +1,706 @@ +/**************************************************************************** +** $Id$ +** +** Implementation of the internal Qt classes dealing with rich text +** +** Created : 990101 +** +** Copyright (C) 1992-2000 Trolltech AS. All rights reserved. +** +** This file is part of the kernel module of the Qt GUI Toolkit. +** +** This file may be distributed under the terms of the Q Public License +** as defined by Trolltech AS of Norway and appearing in the file +** LICENSE.QPL included in the packaging of this file. +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition +** licenses may use this file in accordance with the Qt Commercial License +** Agreement provided with the Software. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for +** information about Qt Commercial License Agreements. +** See http://www.trolltech.com/qpl/ for QPL licensing information. +** See http://www.trolltech.com/gpl/ for GPL licensing information. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#include "qrichtext_p.h" + +using namespace Qt3; + +QTextCommand::~QTextCommand() {} +QTextCommand::Commands QTextCommand::type() const { return Invalid; } + + +QTextCustomItem::~QTextCustomItem() {} +void QTextCustomItem::adjustToPainter( QPainter* p){ if ( p ) width = 0; } +QTextCustomItem::Placement QTextCustomItem::placement() const { return PlaceInline; } + +bool QTextCustomItem::ownLine() const { return FALSE; } +void QTextCustomItem::resize( int nwidth ){ width = nwidth; } +void QTextCustomItem::invalidate() {} + +bool QTextCustomItem::isNested() const { return FALSE; } +int QTextCustomItem::minimumWidth() const { return 0; } + +QString QTextCustomItem::richText() const { return QString::null; } + +bool QTextCustomItem::enter( QTextCursor *, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy, bool atEnd ) +{ + doc = doc; parag = parag; idx = idx; ox = ox; oy = oy; Q_UNUSED( atEnd ) return TRUE; + +} +bool QTextCustomItem::enterAt( QTextCursor *, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy, const QPoint & ) +{ + doc = doc; parag = parag; idx = idx; ox = ox; oy = oy; return TRUE; +} +bool QTextCustomItem::next( QTextCursor *, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy ) +{ + doc = doc; parag = parag; idx = idx; ox = ox; oy = oy; return TRUE; +} +bool QTextCustomItem::prev( QTextCursor *, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy ) +{ + doc = doc; parag = parag; idx = idx; ox = ox; oy = oy; return TRUE; +} +bool QTextCustomItem::down( QTextCursor *, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy ) +{ + doc = doc; parag = parag; idx = idx; ox = ox; oy = oy; return TRUE; +} +bool QTextCustomItem::up( QTextCursor *, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy ) +{ + doc = doc; parag = parag; idx = idx; ox = ox; oy = oy; return TRUE; +} + +void QTextFlow::setPageSize( int ps ) { pagesize = ps; } +bool QTextFlow::isEmpty() { return leftItems.isEmpty() && rightItems.isEmpty(); } + +void QTextTableCell::invalidate() { cached_width = -1; cached_sizehint = -1; } + +void QTextTable::invalidate() { cachewidth = -1; } + +QTextParagData::~QTextParagData() {} +void QTextParagData::join( QTextParagData * ) {} + +QTextFormatter::~QTextFormatter() {} +void QTextFormatter::setWrapEnabled( bool b ) { wrapEnabled = b; } +void QTextFormatter::setWrapAtColumn( int c ) { wrapColumn = c; } + + + +int QTextCursor::x() const +{ + QTextStringChar *c = string->at( idx ); + int curx = c->x; + if ( !c->rightToLeft && + c->c.isSpace() && + idx > 0 && + ( string->alignment() & Qt3::AlignJustify ) == Qt3::AlignJustify ) + curx = string->at( idx - 1 )->x + string->string()->width( idx - 1 ); + if ( c->rightToLeft ) + curx += string->string()->width( idx ); + return curx; +} + +int QTextCursor::y() const +{ + int dummy, line; + string->lineStartOfChar( idx, &dummy, &line ); + return string->lineY( line ); +} + +bool QTextDocument::hasSelection( int id, bool visible ) const +{ + return ( selections.find( id ) != selections.end() && + ( !visible || + ( (QTextDocument*)this )->selectionStartCursor( id ) != + ( (QTextDocument*)this )->selectionEndCursor( id ) ) ); +} + +void QTextDocument::setSelectionStart( int id, QTextCursor *cursor ) +{ + QTextDocumentSelection sel; + sel.startCursor = *cursor; + sel.endCursor = *cursor; + sel.swapped = FALSE; + selections[ id ] = sel; +} + +QTextParag *QTextDocument::paragAt( int i ) const +{ + QTextParag* p = curParag; + if ( !p || p->paragId() > i ) + p = fParag; + while ( p && p->paragId() != i ) + p = p->next(); + ((QTextDocument*)this)->curParag = p; + return p; +} + + +QTextFormat::~QTextFormat() +{ +} + +QTextFormat::QTextFormat() + : fm( QFontMetrics( fn ) ), linkColor( TRUE ), logicalFontSize( 3 ), stdSize( qApp->font().pointSize() ), + different( NoFlags ) +{ + ref = 0; + + usePixelSizes = FALSE; + if ( stdSize == -1 ) { + stdSize = qApp->font().pixelSize(); + usePixelSizes = TRUE; + } + + missp = FALSE; + ha = AlignNormal; + collection = 0; +} + +QTextFormat::QTextFormat( const QStyleSheetItem *style ) + : fm( QFontMetrics( fn ) ), linkColor( TRUE ), logicalFontSize( 3 ), stdSize( qApp->font().pointSize() ), + different( NoFlags ) +{ + ref = 0; + + usePixelSizes = FALSE; + if ( stdSize == -1 ) { + stdSize = qApp->font().pixelSize(); + usePixelSizes = TRUE; + } + + this->style = style->name(); + missp = FALSE; + ha = AlignNormal; + collection = 0; + fn = QFont( style->fontFamily(), + style->fontSize(), + style->fontWeight(), + style->fontItalic() ); + fn.setUnderline( style->fontUnderline() ); + 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(); + updateStyleFlags(); +} + +QTextFormat::QTextFormat( const QFont &f, const QColor &c, QTextFormatCollection *parent ) + : fn( f ), col( c ), fm( QFontMetrics( f ) ), linkColor( TRUE ), + logicalFontSize( 3 ), stdSize( f.pointSize() ), different( NoFlags ) +{ + 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(); + updateStyleFlags(); +} + +QTextFormat::QTextFormat( const QTextFormat &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; + style = f.style; + different = f.different; + addRef(); +} + +QTextFormat& QTextFormat::operator=( const QTextFormat &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; + style = f.style; + different = f.different; + addRef(); + return *this; +} + +void QTextFormat::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(); + updateStyleFlags(); +} + + +QPainter* QTextFormat::pntr = 0; + +void QTextFormat::setPainter( QPainter *p ) +{ + pntr = p; +} + +QPainter* QTextFormat::painter() +{ + return pntr; +} + + +int QTextFormat::minLeftBearing() const +{ + if ( !pntr || !pntr->isActive() ) + return leftBearing; + pntr->setFont( fn ); + return pntr->fontMetrics().minLeftBearing(); +} + +int QTextFormat::minRightBearing() const +{ + if ( !pntr || !pntr->isActive() ) + return rightBearing; + pntr->setFont( fn ); + return pntr->fontMetrics().minRightBearing(); +} + +int QTextFormat::height() const +{ + if ( !pntr || !pntr->isActive() ) + return hei; + pntr->setFont( fn ); + return pntr->fontMetrics().lineSpacing(); +} + +int QTextFormat::ascent() const +{ + if ( !pntr || !pntr->isActive() ) + return asc; + pntr->setFont( fn ); + return pntr->fontMetrics().ascent() + (pntr->fontMetrics().leading()+1)/2; +} + +int QTextFormat::descent() const +{ + if ( !pntr || !pntr->isActive() ) + return dsc; + pntr->setFont( fn ); + return pntr->fontMetrics().descent(); +} + +int QTextFormat::leading() const +{ + if ( !pntr || !pntr->isActive() ) + return fm.leading(); + pntr->setFont( fn ); + return pntr->fontMetrics().leading(); +} + +void QTextFormat::generateKey() +{ + k = getKey( fn, col, isMisspelled(), vAlign() ); +} + +QString QTextFormat::getKey( const QFont &fn, const QColor &col, bool misspelled, VerticalAlignment a ) +{ + QString k = fn.key(); + k += '/'; + k += QString::number( (uint)col.rgb() ); + k += '/'; + k += QString::number( (int)misspelled ); + k += '/'; + k += QString::number( (int)a ); + return k; +} + +void QTextFormat::updateStyle() +{ + if ( !collection || !collection->styleSheet() ) + return; + QStyleSheetItem *item = collection->styleSheet()->item( style ); + if ( !item ) + return; + if ( !( different & Color ) && item->color().isValid() ) + col = item->color(); + if ( !( different & Size ) && item->fontSize() != -1 ) + fn.setPointSize( item->fontSize() ); + if ( !( different & Family ) && !item->fontFamily().isEmpty() ) + fn.setFamily( item->fontFamily() ); + if ( !( different & Bold ) && item->fontWeight() != -1 ) + fn.setWeight( item->fontWeight() ); + if ( !( different & Italic ) && item->definesFontItalic() ) + fn.setItalic( item->fontItalic() ); + if ( !( different & Underline ) && item->definesFontUnderline() ) + fn.setUnderline( item->fontUnderline() ); + generateKey(); + update(); + +} + +void QTextFormat::updateStyleFlags() +{ + different = NoFlags; + if ( !collection || !collection->styleSheet() ) + return; + QStyleSheetItem *item = collection->styleSheet()->item( style ); + if ( !item ) + return; + if ( item->color() != col ) + different |= Color; + if ( item->fontSize() != fn.pointSize() ) + different |= Size; + if ( item->fontFamily() != fn.family() ) + different |= Family; + if ( item->fontItalic() != fn.italic() ) + different |= Italic; + if ( item->fontUnderline() != fn.underline() ) + different |= Underline; + if ( item->fontWeight() != fn.weight() ) + different |= Bold; +} + +QString QTextString::toString( const QMemArray<QTextStringChar> &data ) +{ + QString s; + int l = data.size(); + s.setUnicode( 0, l ); + QTextStringChar *c = data.data(); + QChar *uc = (QChar *)s.unicode(); + while ( l-- ) { + *uc = c->c; + // ### workaround so that non-breaking whitespaces are drawn + // properly, actually this should be fixed in QFont somewhere + if ( *uc == (char)0xa0 ) + *uc = 0x20; + uc++; + c++; + } + + return s; +} + +QString QTextString::toString() const +{ + return toString( data ); +} + +void QTextParag::setSelection( int id, int start, int end ) +{ + QMap<int, QTextParagSelection>::ConstIterator it = selections().find( id ); + if ( it != mSelections->end() ) { + if ( start == ( *it ).start && end == ( *it ).end ) + return; + } + + QTextParagSelection sel; + sel.start = start; + sel.end = end; + (*mSelections)[ id ] = sel; + setChanged( TRUE, TRUE ); +} + +void QTextParag::removeSelection( int id ) +{ + if ( !hasSelection( id ) ) + return; + if ( mSelections ) + mSelections->remove( id ); + setChanged( TRUE, TRUE ); +} + +int QTextParag::selectionStart( int id ) const +{ + if ( !mSelections ) + return -1; + QMap<int, QTextParagSelection>::ConstIterator it = mSelections->find( id ); + if ( it == mSelections->end() ) + return -1; + return ( *it ).start; +} + +int QTextParag::selectionEnd( int id ) const +{ + if ( !mSelections ) + return -1; + QMap<int, QTextParagSelection>::ConstIterator it = mSelections->find( id ); + if ( it == mSelections->end() ) + return -1; + return ( *it ).end; +} + +bool QTextParag::hasSelection( int id ) const +{ + if ( !mSelections ) + return FALSE; + QMap<int, QTextParagSelection>::ConstIterator it = mSelections->find( id ); + if ( it == mSelections->end() ) + return FALSE; + return ( *it ).start != ( *it ).end || length() == 1; +} + +bool QTextParag::fullSelected( int id ) const +{ + if ( !mSelections ) + return FALSE; + QMap<int, QTextParagSelection>::ConstIterator it = mSelections->find( id ); + if ( it == mSelections->end() ) + return FALSE; + return ( *it ).start == 0 && ( *it ).end == str->length() - 1; +} + +int QTextParag::lineY( int l ) const +{ + if ( l > (int)lineStarts.count() - 1 ) { + qWarning( "QTextParag::lineY: line %d out of range!", l ); + return 0; + } + + if ( !isValid() ) + ( (QTextParag*)this )->format(); + + QMap<int, QTextParagLineStart*>::ConstIterator it = lineStarts.begin(); + while ( l-- > 0 ) + ++it; + return ( *it )->y; +} + +int QTextParag::lineBaseLine( int l ) const +{ + if ( l > (int)lineStarts.count() - 1 ) { + qWarning( "QTextParag::lineBaseLine: line %d out of range!", l ); + return 10; + } + + if ( !isValid() ) + ( (QTextParag*)this )->format(); + + QMap<int, QTextParagLineStart*>::ConstIterator it = lineStarts.begin(); + while ( l-- > 0 ) + ++it; + return ( *it )->baseLine; +} + +int QTextParag::lineHeight( int l ) const +{ + if ( l > (int)lineStarts.count() - 1 ) { + qWarning( "QTextParag::lineHeight: line %d out of range!", l ); + return 15; + } + + if ( !isValid() ) + ( (QTextParag*)this )->format(); + + QMap<int, QTextParagLineStart*>::ConstIterator it = lineStarts.begin(); + while ( l-- > 0 ) + ++it; + return ( *it )->h; +} + +void QTextParag::lineInfo( int l, int &y, int &h, int &bl ) const +{ + if ( l > (int)lineStarts.count() - 1 ) { + qWarning( "QTextParag::lineInfo: line %d out of range!", l ); + qDebug( "%d %d", (int)lineStarts.count() - 1, l ); + y = 0; + h = 15; + bl = 10; + return; + } + + if ( !isValid() ) + ( (QTextParag*)this )->format(); + + QMap<int, QTextParagLineStart*>::ConstIterator it = lineStarts.begin(); + while ( l-- > 0 ) + ++it; + y = ( *it )->y; + h = ( *it )->h; + bl = ( *it )->baseLine; +} + +int QTextParag::alignment() const +{ + if ( align != -1 ) + return align; + QStyleSheetItem *item = style(); + if ( !item ) + return Qt3::AlignAuto; + if ( mStyleSheetItemsVec ) { + for ( int i = 0; i < (int)mStyleSheetItemsVec->size(); ++i ) { + item = (*mStyleSheetItemsVec)[ i ]; + if ( item->alignment() != QStyleSheetItem::Undefined ) + return item->alignment(); + } + } + return Qt3::AlignAuto; +} + +QPtrVector<QStyleSheetItem> QTextParag::styleSheetItems() const +{ + QPtrVector<QStyleSheetItem> vec; + if ( mStyleSheetItemsVec ) { + vec.resize( mStyleSheetItemsVec->size() ); + for ( int i = 0; i < (int)vec.size(); ++i ) + vec.insert( i, (*mStyleSheetItemsVec)[ i ] ); + } + return vec; +} + +QStyleSheetItem *QTextParag::style() const +{ + if ( !mStyleSheetItemsVec || mStyleSheetItemsVec->size() == 0 ) + return 0; + return (*mStyleSheetItemsVec)[ mStyleSheetItemsVec->size() - 1 ]; +} + +int QTextParag::numberOfSubParagraph() const +{ + if ( list_val != -1 ) + return list_val; + if ( numSubParag != -1 ) + return numSubParag; + int n = 0; + QTextParag *p = (QTextParag*)this; + while ( p && ( styleSheetItemsVec().size() >= p->styleSheetItemsVec().size() && + styleSheetItemsVec()[ (int)p->styleSheetItemsVec().size() - 1 ] == p->style() || + p->styleSheetItemsVec().size() >= styleSheetItemsVec().size() && + p->styleSheetItemsVec()[ (int)styleSheetItemsVec().size() - 1 ] == style() ) ) { + if ( p->style() == style() && listStyle() != p->listStyle() + && p->styleSheetItemsVec().size() == styleSheetItemsVec().size() ) + break; + if ( p->style()->displayMode() == QStyleSheetItem::DisplayListItem + && p->style() != style() || styleSheetItemsVec().size() == p->styleSheetItemsVec().size() ) + ++n; + p = p->prev(); + } + ( (QTextParag*)this )->numSubParag = n; + return n; +} + +void QTextParag::setFormat( QTextFormat *fm ) +{ + bool doUpdate = FALSE; + if (defFormat && (defFormat != formatCollection()->defaultFormat())) + doUpdate = TRUE; + defFormat = formatCollection()->format( fm ); + if ( !doUpdate ) + return; + for ( int i = 0; i < length(); ++i ) { + if ( at( i )->format()->styleName() == defFormat->styleName() ) + at( i )->format()->updateStyle(); + } +} + +QTextFormatter *QTextParag::formatter() const +{ + if ( hasdoc ) + return document()->formatter(); + if ( pseudoDocument()->pFormatter ) + return pseudoDocument()->pFormatter; + return ( ( (QTextParag*)this )->pseudoDocument()->pFormatter = new QTextFormatterBreakWords ); +} + +void QTextParag::setTabArray( int *a ) +{ + delete [] tArray; + tArray = a; +} + +void QTextParag::setTabStops( int tw ) +{ + if ( hasdoc ) + document()->setTabStops( tw ); + else + tabStopWidth = tw; +} + +QMap<int, QTextParagSelection> &QTextParag::selections() const +{ + if ( !mSelections ) + ((QTextParag *)this)->mSelections = new QMap<int, QTextParagSelection>; + return *mSelections; +} + +QPtrVector<QStyleSheetItem> &QTextParag::styleSheetItemsVec() const +{ + if ( !mStyleSheetItemsVec ) + ((QTextParag *)this)->mStyleSheetItemsVec = new QPtrVector<QStyleSheetItem>; + return *mStyleSheetItemsVec; +} + +QPtrList<QTextCustomItem> &QTextParag::floatingItems() const +{ + if ( !mFloatingItems ) + ((QTextParag *)this)->mFloatingItems = new QPtrList<QTextCustomItem>; + return *mFloatingItems; +} + +QTextStringChar::~QTextStringChar() +{ + if ( format() ) + format()->removeRef(); + if ( type ) // not Regular + delete d.custom; +} + +QTextParagPseudoDocument::QTextParagPseudoDocument():pFormatter(0),commandHistory(0), minw(0),wused(0){} +QTextParagPseudoDocument::~QTextParagPseudoDocument(){ delete pFormatter; delete commandHistory; } diff --git a/noncore/apps/opie-write/qrichtext_p.h b/noncore/apps/opie-write/qrichtext_p.h new file mode 100644 index 0000000..94ce913 --- a/dev/null +++ b/noncore/apps/opie-write/qrichtext_p.h @@ -0,0 +1,2158 @@ +/**************************************************************************** +** $Id$ +** +** Definition of internal rich text classes +** +** Created : 990124 +** +** Copyright (C) 1999-2000 Trolltech AS. All rights reserved. +** +** This file is part of the kernel module of the Qt GUI Toolkit. +** +** This file may be distributed under the terms of the Q Public License +** as defined by Trolltech AS of Norway and appearing in the file +** LICENSE.QPL included in the packaging of this file. +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition +** licenses may use this file in accordance with the Qt Commercial License +** Agreement provided with the Software. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for +** information about Qt Commercial License Agreements. +** See http://www.trolltech.com/qpl/ for QPL licensing information. +** See http://www.trolltech.com/gpl/ for GPL licensing information. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#ifndef QRICHTEXT_P_H +#define QRICHTEXT_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. +// +// + +#ifndef QT_H +#include "qt3namespace.h" +#include "qstring.h" +#include "qlist.h" +#include "qrect.h" +#include "qfontmetrics.h" +#include "qintdict.h" +#include "qmap.h" +#include "qstringlist.h" +#include "qfont.h" +#include "qcolor.h" +#include "qsize.h" +#include "qvaluelist.h" +#include "qvaluestack.h" +#include "qobject.h" +#include "qdict.h" +#include "qtextstream.h" +#include "qpixmap.h" +#include "qstylesheet.h" +#include "qvector.h" +#include "qpainter.h" +#include "qlayout.h" +#include "qobject.h" +#include "qcomplextext_p.h" +#include "qapplication.h" +#include <limits.h> +#endif // QT_H + +//#define DEBUG_COLLECTION + +namespace Qt3 { + +class QTextDocument; +class QTextString; +class QTextPreProcessor; +class QTextFormat; +class QTextCursor; +class QTextParag; +class QTextFormatter; +class QTextIndent; +class QTextFormatCollection; +class QStyleSheetItem; +class QTextCustomItem; +class QTextFlow; +struct QBidiContext; + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_EXPORT QTextStringChar +{ + friend class QTextString; + +public: + // this is never called, initialize variables in QTextString::insert()!!! + QTextStringChar() : lineStart( 0 ), type( Regular ), startOfRun( 0 ) {d.format=0;} + ~QTextStringChar(); + + QChar c; + enum Type { Regular=0, Custom=1, Anchor=2, CustomAnchor=3 }; + uint lineStart : 1; + uint rightToLeft : 1; + uint hasCursor : 1; + uint canBreak : 1; + Type type : 2; + uint startOfRun : 1; + + int x; + int height() const; + int ascent() const; + int descent() const; + bool isCustom() const { return (type & Custom) != 0; } + QTextFormat *format() const; + QTextCustomItem *customItem() const; + void setFormat( QTextFormat *f ); + void setCustomItem( QTextCustomItem *i ); + QTextStringChar *clone() const; + struct CustomData + { + QTextFormat *format; + QTextCustomItem *custom; + QString anchorName; + QString anchorHref; + }; + + void loseCustomItem(); + + union { + QTextFormat* format; + CustomData* custom; + } d; + + bool isAnchor() const { return ( type & Anchor) != 0; } + QString anchorName() const; + QString anchorHref() const; + void setAnchor( const QString& name, const QString& href ); + +private: + QTextStringChar &operator=( const QTextStringChar & ) { + //abort(); + return *this; + } + friend class QComplexText; + friend class QTextParag; +}; + +#if defined(Q_TEMPLATEDLL) +// MOC_SKIP_BEGIN +template class Q_EXPORT QMemArray<QTextStringChar>; +// MOC_SKIP_END +#endif + +class Q_EXPORT QTextString +{ +public: + + QTextString(); + QTextString( const QTextString &s ); + virtual ~QTextString(); + + static QString toString( const QMemArray<QTextStringChar> &data ); + QString toString() const; + + QTextStringChar &at( int i ) const; + int length() const; + + int width( int idx ) const; + + void insert( int index, const QString &s, QTextFormat *f ); + void insert( int index, QTextStringChar *c ); + void truncate( int index ); + void remove( int index, int len ); + void clear(); + + void setFormat( int index, QTextFormat *f, bool useCollection ); + + void setBidi( bool b ) { bidi = b; } + bool isBidi() const; + bool isRightToLeft() const; + QChar::Direction direction() const; + void setDirection( QChar::Direction d ) { dir = d; bidiDirty = TRUE; } + + QMemArray<QTextStringChar> subString( int start = 0, int len = 0xFFFFFF ) const; + QMemArray<QTextStringChar> rawData() const { return data; } + + void operator=( const QString &s ) { clear(); insert( 0, s, 0 ); } + void operator+=( const QString &s ); + void prepend( const QString &s ) { insert( 0, s, 0 ); } + +private: + void checkBidi() const; + + QMemArray<QTextStringChar> data; + uint bidiDirty : 1; + uint bidi : 1; // true when the paragraph has right to left characters + uint rightToLeft : 1; + uint dir : 5; +}; + +inline bool QTextString::isBidi() const +{ + if ( bidiDirty ) + checkBidi(); + return bidi; +} + +inline bool QTextString::isRightToLeft() const +{ + if ( bidiDirty ) + checkBidi(); + return rightToLeft; +} + +inline QChar::Direction QTextString::direction() const +{ + return (QChar::Direction) dir; +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +#if defined(Q_TEMPLATEDLL) +// MOC_SKIP_BEGIN +template class Q_EXPORT QValueStack<int>; +template class Q_EXPORT QValueStack<QTextParag*>; +template class Q_EXPORT QValueStack<bool>; +// MOC_SKIP_END +#endif + +class Q_EXPORT QTextCursor +{ +public: + QTextCursor( QTextDocument *d ); + QTextCursor(); + QTextCursor( const QTextCursor &c ); + QTextCursor &operator=( const QTextCursor &c ); + virtual ~QTextCursor() {} + + bool operator==( const QTextCursor &c ) const; + bool operator!=( const QTextCursor &c ) const { return !(*this == c); } + + QTextDocument *document() const { return doc; } + void setDocument( QTextDocument *d ); + + QTextParag *parag() const; + int index() const; + void setParag( QTextParag *s, bool restore = TRUE ); + + 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(); + void gotoPreviousWord(); + void gotoWordLeft(); + void gotoWordRight(); + + void insert( const QString &s, bool checkNewLine, QMemArray<QTextStringChar> *formatting = 0 ); + void splitAndInsertEmptyParag( bool ind = TRUE, bool updateIds = TRUE ); + bool remove(); + void killLine(); + void indent(); + + bool atParagStart(); + bool atParagEnd(); + + void setIndex( int i, bool restore = TRUE ); + + void checkIndex(); + + int offsetX() const { return ox; } + int offsetY() const { return oy; } + + QTextParag *topParag() const { return parags.isEmpty() ? string : parags.first(); } + int totalOffsetX() const; + int totalOffsetY() const; + + bool place( const QPoint &pos, QTextParag *s ) { return place( pos, s, FALSE ); } + bool place( const QPoint &pos, QTextParag *s, bool link ); + void restoreState(); + + int x() const; + int y() const; + + 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; } + +private: + enum Operation { EnterBegin, EnterEnd, Next, Prev, Up, Down }; + + void push(); + void pop(); + void processNesting( Operation op ); + void invalidateNested(); + void gotoIntoNested( const QPoint &globalPos ); + + QTextParag *string; + QTextDocument *doc; + int idx, tmpIndex; + int ox, oy; + QValueStack<int> indices; + QValueStack<QTextParag*> parags; + QValueStack<int> xOffsets; + QValueStack<int> yOffsets; + QValueStack<bool> nestedStack; + uint nested : 1; + uint valid : 1; + +}; + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_EXPORT QTextCommand +{ +public: + enum Commands { Invalid, Insert, Delete, Format, Alignment, ParagType }; + + QTextCommand( QTextDocument *d ) : doc( d ), cursor( d ) {} + virtual ~QTextCommand(); + + virtual Commands type() const; + + virtual QTextCursor *execute( QTextCursor *c ) = 0; + virtual QTextCursor *unexecute( QTextCursor *c ) = 0; + +protected: + QTextDocument *doc; + QTextCursor cursor; + +}; + +#if defined(Q_TEMPLATEDLL) +// MOC_SKIP_BEGIN +template class Q_EXPORT QPtrList<QTextCommand>; +// MOC_SKIP_END +#endif + +class Q_EXPORT QTextCommandHistory +{ +public: + QTextCommandHistory( int s ) : current( -1 ), steps( s ) { history.setAutoDelete( TRUE ); } + virtual ~QTextCommandHistory(); + + void clear() { history.clear(); current = -1; } + + void addCommand( QTextCommand *cmd ); + QTextCursor *undo( QTextCursor *c ); + QTextCursor *redo( QTextCursor *c ); + + bool isUndoAvailable(); + bool isRedoAvailable(); + + void setUndoDepth( int d ) { steps = d; } + int undoDepth() const { return steps; } + + int historySize() const { return history.count(); } + int currentPosition() const { return current; } + +private: + QPtrList<QTextCommand> history; + int current, steps; + +}; + +inline QTextCommandHistory::~QTextCommandHistory() +{ + clear(); +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_EXPORT QTextCustomItem +{ +public: + QTextCustomItem( QTextDocument *p ) + : xpos(0), ypos(-1), width(-1), height(0), parent( p ) + {} + virtual ~QTextCustomItem(); + virtual void draw(QPainter* p, int x, int y, int cx, int cy, int cw, int ch, const QColorGroup& cg, 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( QTextCursor *, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy, bool atEnd = FALSE ); + virtual bool enterAt( QTextCursor *, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy, const QPoint & ); + virtual bool next( QTextCursor *, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy ); + virtual bool prev( QTextCursor *, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy ); + virtual bool down( QTextCursor *, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy ); + virtual bool up( QTextCursor *, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy ); + + void setParagraph( QTextParag *p ) { parag = p; } + QTextParag *paragrapth() const { return parag; } + + QTextDocument *parent; + QTextParag *parag; + + virtual void pageBreak( int y, QTextFlow* flow ); +}; + +#if defined(Q_TEMPLATEDLL) +// MOC_SKIP_BEGIN +template class Q_EXPORT QMap<QString, QString>; +// MOC_SKIP_END +#endif + +class Q_EXPORT QTextImage : public QTextCustomItem +{ +public: + QTextImage( QTextDocument *p, const QMap<QString, QString> &attr, const QString& context, + QMimeSourceFactory &factory ); + virtual ~QTextImage(); + + 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 QColorGroup& cg, bool selected ); + +private: + QRegion* reg; + QPixmap pm; + Placement place; + int tmpwidth, tmpheight; + QMap<QString, QString> attributes; + QString imgId; + +}; + +class Q_EXPORT QTextHorizontalLine : public QTextCustomItem +{ +public: + QTextHorizontalLine( QTextDocument *p, const QMap<QString, QString> &attr, const QString& context, + QMimeSourceFactory &factory ); + virtual ~QTextHorizontalLine(); + + void adjustToPainter( QPainter* ); + void draw(QPainter* p, int x, int y, int cx, int cy, int cw, int ch, const QColorGroup& cg, bool selected ); + QString richText() const; + + bool ownLine() const { return TRUE; } + +private: + int tmpheight; + QColor color; + +}; + +#if defined(Q_TEMPLATEDLL) +// MOC_SKIP_BEGIN +template class Q_EXPORT QPtrList<QTextCustomItem>; +// MOC_SKIP_END +#endif + +class Q_EXPORT QTextFlow +{ + friend class QTextDocument; + friend class QTextTableCell; + +public: + QTextFlow(); + virtual ~QTextFlow(); + + 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 ); + + virtual void registerFloatingItem( QTextCustomItem* item ); + virtual void unregisterFloatingItem( QTextCustomItem* item ); + virtual QRect boundingRect() const; + virtual void drawFloatingItems(QPainter* p, int cx, int cy, int cw, int ch, const QColorGroup& cg, 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; + + QPtrList<QTextCustomItem> leftItems; + QPtrList<QTextCustomItem> rightItems; + +}; + +inline int QTextFlow::width() const { return w; } + +class QTextTable; + +class Q_EXPORT QTextTableCell : public QLayoutItem +{ + friend class QTextTable; + +public: + QTextTableCell( QTextTable* table, + int row, int column, + const QMap<QString, QString> &attr, + const QStyleSheetItem* style, + const QTextFormat& fmt, const QString& context, + QMimeSourceFactory &factory, QStyleSheet *sheet, const QString& doc ); + QTextTableCell( QTextTable* table, int row, int column ); + virtual ~QTextTableCell(); + + QSize sizeHint() const ; + QSize minimumSize() const ; + QSize maximumSize() const ; + QSizePolicy::ExpandData expanding() 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_; } + + QTextDocument* richText() const { return richtext; } + QTextTable* table() const { return parent; } + + void draw( QPainter* p, int x, int y, int cx, int cy, int cw, int ch, const QColorGroup& cg, bool selected ); + + QBrush *backGround() const { return background; } + virtual void invalidate(); + + int verticalAlignmentOffset() const; + int horizontalAlignmentOffset() const; + +private: + QRect geom; + QTextTable* parent; + QTextDocument* 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; +}; + +#if defined(Q_TEMPLATEDLL) +// MOC_SKIP_BEGIN +template class Q_EXPORT QPtrList<QTextTableCell>; +template class Q_EXPORT QMap<QTextCursor*, int>; +// MOC_SKIP_END +#endif + +class Q_EXPORT QTextTable: public QTextCustomItem +{ + friend class QTextTableCell; + +public: + QTextTable( QTextDocument *p, const QMap<QString, QString> &attr ); + virtual ~QTextTable(); + + void adjustToPainter( QPainter *p ); + void pageBreak( int y, QTextFlow* flow ); + void draw( QPainter* p, int x, int y, int cx, int cy, int cw, int ch, + const QColorGroup& cg, 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( QTextCursor *c, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy, bool atEnd = FALSE ); + virtual bool enterAt( QTextCursor *c, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy, const QPoint &pos ); + virtual bool next( QTextCursor *c, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy ); + virtual bool prev( QTextCursor *c, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy ); + virtual bool down( QTextCursor *c, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy ); + virtual bool up( QTextCursor *c, QTextDocument *&doc, QTextParag *¶g, int &idx, int &ox, int &oy ); + + QString richText() const; + + int minimumWidth() const; + + QPtrList<QTextTableCell> tableCells() const { return cells; } + + bool isStretching() const { return stretch; } + +private: + void format( int w ); + void addCell( QTextTableCell* cell ); + +private: + QGridLayout* layout; + QPtrList<QTextTableCell> 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; + QMap<QString, QString> attributes; + QMap<QTextCursor*, int> currCell; + Placement place; + void adjustCells( int y , int shift ); + int pageBreakFor; +}; + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class QTextTableCell; +class QTextParag; + +struct Q_EXPORT QTextDocumentSelection +{ + QTextCursor startCursor, endCursor; + bool swapped; +}; + +#if defined(Q_TEMPLATEDLL) +// MOC_SKIP_BEGIN +template class Q_EXPORT QMap<int, QColor>; +template class Q_EXPORT QMap<int, bool>; +template class Q_EXPORT QMap<int, QTextDocumentSelection>; +template class Q_EXPORT QPtrList<QTextDocument>; +// MOC_SKIP_END +#endif + +class Q_EXPORT QTextDocument : public QObject +{ + Q_OBJECT + + friend class QTextTableCell; + friend class QTextCursor; + friend class QTextEdit; + friend class QTextParag; + +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() + }; + + QTextDocument( QTextDocument *p ); + QTextDocument( QTextDocument *d, QTextFormatCollection *f ); + virtual ~QTextDocument(); + + QTextDocument *parent() const { return par; } + QTextParag *parentParag() const { return parParag; } + + 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, QTextParag *parag = 0 ); + + void setY( int y ); + int leftMargin() const; + void setLeftMargin( int lm ); + int rightMargin() const; + void setRightMargin( int rm ); + + QTextParag *firstParag() const; + QTextParag *lastParag() const; + void setFirstParag( QTextParag *p ); + void setLastParag( QTextParag *p ); + + void invalidate(); + + void setPreProcessor( QTextPreProcessor *sh ); + QTextPreProcessor *preProcessor() const; + + void setFormatter( QTextFormatter *f ); + QTextFormatter *formatter() const; + + void setIndent( QTextIndent *i ); + QTextIndent *indent() const; + + QColor selectionColor( int id ) const; + bool invertSelectionText( int id ) const; + void setSelectionColor( int id, const QColor &c ); + void setInvertSelectionText( int id, bool b ); + bool hasSelection( int id, bool visible = FALSE ) const; + void setSelectionStart( int id, QTextCursor *cursor ); + bool setSelectionEnd( int id, QTextCursor *cursor ); + void selectAll( int id ); + bool removeSelection( int id ); + void selectionStart( int id, int ¶gId, int &index ); + QTextCursor selectionStartCursor( int id ); + QTextCursor selectionEndCursor( int id ); + void selectionEnd( int id, int ¶gId, int &index ); + void setFormat( int id, QTextFormat *f, int flags ); + QTextParag *selectionStart( int id ); + QTextParag *selectionEnd( int id ); + int numSelections() const { return nSelections; } + void addSelection( int id ); + + QString selectedText( int id, bool withCustom = TRUE ) const; + void copySelectedText( int id ); + void removeSelectedText( int id, QTextCursor *cursor ); + void indentSelection( int id ); + + QTextParag *paragAt( int i ) const; + + void addCommand( QTextCommand *cmd ); + QTextCursor *undo( QTextCursor *c = 0 ); + QTextCursor *redo( QTextCursor *c = 0 ); + QTextCommandHistory *commands() const { return commandHistory; } + + QTextFormatCollection *formatCollection() const; + + bool find( const QString &expr, bool cs, bool wo, bool forward, int *parag, int *index, QTextCursor *cursor ); + + void setTextFormat( Qt::TextFormat f ); + Qt::TextFormat textFormat() const; + + bool inSelection( int selId, const QPoint &pos ) const; + + QStyleSheet *styleSheet() const { return sheet_; } + QMimeSourceFactory *mimeSourceFactory() const { return factory_; } + QString context() const { return contxt; } + + void setStyleSheet( QStyleSheet *s ); + void updateStyles(); + void updateFontSizes( int base, bool usePixels ); + void updateFontAttributes( const QFont &f, const QFont &old ); + void setMimeSourceFactory( QMimeSourceFactory *f ) { if ( f ) factory_ = f; } + void setContext( const QString &c ) { if ( !c.isEmpty() ) contxt = c; } + + void setUnderlineLinks( bool b ) { underlLinks = 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 QColorGroup &cg, const QBrush *paper = 0 ); + void drawParag( QPainter *p, QTextParag *parag, int cx, int cy, int cw, int ch, + QPixmap *&doubleBuffer, const QColorGroup &cg, + bool drawCursor, QTextCursor *cursor, bool resetChanged = TRUE ); + QTextParag *draw( QPainter *p, int cx, int cy, int cw, int ch, const QColorGroup &cg, + bool onlyChanged = FALSE, bool drawCursor = FALSE, QTextCursor *cursor = 0, + bool resetChanged = TRUE ); + + void setDefaultFont( const QFont &f ); + + void registerCustomItem( QTextCustomItem *i, QTextParag *p ); + void unregisterCustomItem( QTextCustomItem *i, QTextParag *p ); + + void setFlow( QTextFlow *f ); + void takeFlow(); + QTextFlow *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; } + + QTextTableCell *tableCell() const { return tc; } + void setTableCell( QTextTableCell *c ) { tc = c; } + + void setPlainText( const QString &text ); + void setRichText( const QString &text, const QString &context ); + QString richText( QTextParag *p = 0 ) const; + QString plainText( QTextParag *p = 0 ) 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 d ) { commandHistory->setUndoDepth( d ); } + int undoDepth() const { return commandHistory->undoDepth(); } + + int length() const; + void clear( bool createEmptyParag = FALSE ); + + virtual QTextParag *createParag( QTextDocument *d, QTextParag *pr = 0, QTextParag *nx = 0, bool updateIds = TRUE ); + void insertChild( QObject *o ) { QObject::insertChild( o ); } + void removeChild( QObject *o ) { QObject::removeChild( o ); } + void insertChild( QTextDocument *d ) { childList.append( d ); } + void removeChild( QTextDocument *d ) { childList.removeRef( d ); } + QPtrList<QTextDocument> children() const { return childList; } + + void setAddMargins( bool b ) { addMargs = b; } + int addMargins() const { return addMargs; } + + bool hasFocusParagraph() const; + QString focusHref() const; + + void invalidateOriginalText() { oTextValid = FALSE; oText = ""; } + +signals: + void minimumWidthChanged( int ); + +private: + 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); + QTextCustomItem* parseTable( const QMap<QString, QString> &attr, const QTextFormat &fmt, + const QChar* doc, int length, int& pos, QTextParag *curpar ); + 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, QStyleSheetItem::WhiteSpaceMode wsm ); + void setRichTextInternal( const QString &text ); + +private: + struct Q_EXPORT Focus { + QTextParag *parag; + int start, len; + QString href; + }; + + int cx, cy, cw, vw; + QTextParag *fParag, *lParag; + QTextPreProcessor *pProcessor; + QMap<int, QColor> selectionColors; + QMap<int, QTextDocumentSelection> selections; + QMap<int, bool> selectionText; + QTextCommandHistory *commandHistory; + QTextFormatter *pFormatter; + QTextIndent *indenter; + QTextFormatCollection *fCollection; + Qt::TextFormat txtFormat; + uint preferRichText : 1; + uint pages : 1; + uint useFC : 1; + uint withoutDoubleBuffer : 1; + uint underlLinks : 1; + uint nextDoubleBuffered : 1; + uint addMargs : 1; + uint oTextValid : 1; + uint mightHaveCustomItems : 1; + int align; + int nSelections; + QTextFlow *flow_; + QTextDocument *par; + QTextParag *parParag; + QTextTableCell *tc; + QTextCursor *tmpCursor; + QBrush *backBrush; + QPixmap *buf_pixmap; + Focus focusIndicator; + int minw; + int wused; + int leftmargin; + int rightmargin; + QTextParag *minwParag, *curParag; + QStyleSheet* sheet_; + QMimeSourceFactory* factory_; + QString contxt; + QMap<QString, QString> attribs; + int *tArray; + int tStopWidth; + int uDepth; + QString oText; + QPtrList<QTextDocument> childList; + QColor linkColor; + +}; + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +class Q_EXPORT QTextDeleteCommand : public QTextCommand +{ +public: + QTextDeleteCommand( QTextDocument *d, int i, int idx, const QMemArray<QTextStringChar> &str, + const QValueList< QPtrVector<QStyleSheetItem> > &os, + const QValueList<QStyleSheetItem::ListStyle> &ols, + const QMemArray<int> &oas ); + QTextDeleteCommand( QTextParag *p, int idx, const QMemArray<QTextStringChar> &str ); + virtual ~QTextDeleteCommand(); + + Commands type() const { return Delete; } + QTextCursor *execute( QTextCursor *c ); + QTextCursor *unexecute( QTextCursor *c ); + +protected: + int id, index; + QTextParag *parag; + QMemArray<QTextStringChar> text; + QValueList< QPtrVector<QStyleSheetItem> > oldStyles; + QValueList<QStyleSheetItem::ListStyle> oldListStyles; + QMemArray<int> oldAligns; + +}; + +class Q_EXPORT QTextInsertCommand : public QTextDeleteCommand +{ +public: + QTextInsertCommand( QTextDocument *d, int i, int idx, const QMemArray<QTextStringChar> &str, + const QValueList< QPtrVector<QStyleSheetItem> > &os, + const QValueList<QStyleSheetItem::ListStyle> &ols, + const QMemArray<int> &oas ) + : QTextDeleteCommand( d, i, idx, str, os, ols, oas ) {} + QTextInsertCommand( QTextParag *p, int idx, const QMemArray<QTextStringChar> &str ) + : QTextDeleteCommand( p, idx, str ) {} + virtual ~QTextInsertCommand() {} + + Commands type() const { return Insert; } + QTextCursor *execute( QTextCursor *c ) { return QTextDeleteCommand::unexecute( c ); } + QTextCursor *unexecute( QTextCursor *c ) { return QTextDeleteCommand::execute( c ); } + +}; + +class Q_EXPORT QTextFormatCommand : public QTextCommand +{ +public: + QTextFormatCommand( QTextDocument *d, int sid, int sidx, int eid, int eidx, const QMemArray<QTextStringChar> &old, QTextFormat *f, int fl ); + virtual ~QTextFormatCommand(); + + Commands type() const { return Format; } + QTextCursor *execute( QTextCursor *c ); + QTextCursor *unexecute( QTextCursor *c ); + +protected: + int startId, startIndex, endId, endIndex; + QTextFormat *format; + QMemArray<QTextStringChar> oldFormats; + int flags; + +}; + +class Q_EXPORT QTextAlignmentCommand : public QTextCommand +{ +public: + QTextAlignmentCommand( QTextDocument *d, int fParag, int lParag, int na, const QMemArray<int> &oa ); + virtual ~QTextAlignmentCommand() {} + + Commands type() const { return Alignment; } + QTextCursor *execute( QTextCursor *c ); + QTextCursor *unexecute( QTextCursor *c ); + +private: + int firstParag, lastParag; + int newAlign; + QMemArray<int> oldAligns; + +}; + +class Q_EXPORT QTextParagTypeCommand : public QTextCommand +{ +public: + QTextParagTypeCommand( QTextDocument *d, int fParag, int lParag, bool l, + QStyleSheetItem::ListStyle s, const QValueList< QPtrVector<QStyleSheetItem> > &os, + const QValueList<QStyleSheetItem::ListStyle> &ols ); + virtual ~QTextParagTypeCommand() {} + + Commands type() const { return ParagType; } + QTextCursor *execute( QTextCursor *c ); + QTextCursor *unexecute( QTextCursor *c ); + +private: + int firstParag, lastParag; + bool list; + QStyleSheetItem::ListStyle listStyle; + QValueList< QPtrVector<QStyleSheetItem> > oldStyles; + QValueList<QStyleSheetItem::ListStyle> oldListStyles; + +}; + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +struct Q_EXPORT QTextParagSelection +{ + int start, end; +}; + +struct Q_EXPORT QTextParagLineStart +{ + QTextParagLineStart() : y( 0 ), baseLine( 0 ), h( 0 ) +#ifndef QT_NO_COMPLEXTEXT + , bidicontext( 0 ) +#endif + { } + QTextParagLineStart( ushort y_, ushort bl, ushort h_ ) : y( y_ ), baseLine( bl ), h( h_ ), + w( 0 ) +#ifndef QT_NO_COMPLEXTEXT + , bidicontext( 0 ) +#endif + { } +#ifndef QT_NO_COMPLEXTEXT + QTextParagLineStart( QBidiContext *c, QBidiStatus s ) : y(0), baseLine(0), h(0), + status( s ), bidicontext( c ) { if ( bidicontext ) bidicontext->ref(); } +#endif + + virtual ~QTextParagLineStart() + { +#ifndef QT_NO_COMPLEXTEXT + if ( bidicontext && bidicontext->deref() ) + delete bidicontext; +#endif + } + +#ifndef QT_NO_COMPLEXTEXT + void setContext( QBidiContext *c ) { + if ( c == bidicontext ) + return; + if ( bidicontext && bidicontext->deref() ) + delete bidicontext; + bidicontext = c; + if ( bidicontext ) + bidicontext->ref(); + } + QBidiContext *context() const { return bidicontext; } +#endif + +public: + ushort y, baseLine, h; +#ifndef QT_NO_COMPLEXTEXT + QBidiStatus status; +#endif + int w; + +private: +#ifndef QT_NO_COMPLEXTEXT + QBidiContext *bidicontext; +#endif +}; + +#if defined(Q_TEMPLATEDLL) +// MOC_SKIP_BEGIN +template class Q_EXPORT QMap<int, QTextParagSelection>; +template class Q_EXPORT QMap<int, QTextParagLineStart*>; +// MOC_SKIP_END +#endif + +class Q_EXPORT QTextParagData +{ +public: + QTextParagData() {} + virtual ~QTextParagData(); + virtual void join( QTextParagData * ); +}; + +class Q_EXPORT QTextParagPseudoDocument +{ +public: + QTextParagPseudoDocument(); + ~QTextParagPseudoDocument(); + QRect docRect; + QTextFormatter *pFormatter; + QTextCommandHistory *commandHistory; + int minw; + int wused; +}; + +//nase +class Q_EXPORT QTextParag +{ + friend class QTextDocument; + friend class QTextCursor; + +public: + QTextParag( QTextDocument *d, QTextParag *pr = 0, QTextParag *nx = 0, bool updateIds = TRUE ); + virtual ~QTextParag(); + + QTextString *string() const; + QTextStringChar *at( int i ) const; // maybe remove later + int leftGap() const; + int length() const; // maybe remove later + + void setListStyle( QStyleSheetItem::ListStyle ls ); + QStyleSheetItem::ListStyle listStyle() const; + void setListValue( int v ) { list_val = v; } + int listValue() const { return list_val; } + + void setList( bool b, int listStyle ); + void incDepth(); + void decDepth(); + int listDepth() const; + + void setFormat( QTextFormat *fm ); + QTextFormat *paragFormat() const; + + QTextDocument *document() const; + QTextParagPseudoDocument *pseudoDocument() const; + + QRect rect() const; + void setHeight( int h ) { r.setHeight( h ); } + void show(); + void hide(); + bool isVisible() const { return visible; } + + QTextParag *prev() const; + QTextParag *next() const; + void setPrev( QTextParag *s ); + void setNext( QTextParag *s ); + + void insert( int index, const QString &s ); + void append( const QString &s, bool reallyAtEnd = FALSE ); + void truncate( int index ); + void remove( int index, int len ); + void join( QTextParag *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; + QTextStringChar *lineStartOfChar( int i, int *index = 0, int *line = 0 ) const; + int lines() const; + QTextStringChar *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( QTextParagData *data ); + QTextParagData *extraData() const; + + QMap<int, QTextParagLineStart*> &lineStartList(); + + void setFormat( int index, int len, QTextFormat *f, bool useCollection = TRUE, int flags = -1 ); + + void setAlignment( int a ); + int alignment() const; + + virtual void paint( QPainter &painter, const QColorGroup &cg, QTextCursor *cursor = 0, bool drawSelections = FALSE, + int clipx = -1, int clipy = -1, int clipw = -1, int cliph = -1 ); + + void setStyleSheetItems( const QPtrVector<QStyleSheetItem> &vec ); + QPtrVector<QStyleSheetItem> styleSheetItems() const; + QStyleSheetItem *style() const; + + virtual int topMargin() const; + virtual int bottomMargin() const; + virtual int leftMargin() const; + virtual int firstLineMargin() const; + virtual int rightMargin() const; + virtual int lineSpacing() const; + + int numberOfSubParagraph() const; + void registerFloatingItem( QTextCustomItem *i ); + void unregisterFloatingItem( QTextCustomItem *i ); + + void setFullWidth( bool b ) { fullWidth = b; } + bool isFullWidth() const { return fullWidth; } + + QTextTableCell *tableCell() const; + + QBrush *background() const; + + int documentWidth() const; + int documentVisibleWidth() const; + int documentX() const; + int documentY() const; + QTextFormatCollection *formatCollection() const; + QTextFormatter *formatter() const; + + virtual 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( QTextCommand *cmd ); + QTextCursor *undo( QTextCursor *c = 0 ); + QTextCursor *redo( QTextCursor *c = 0 ); + QTextCommandHistory *commands() const; + virtual void copyParagData( QTextParag *parag ); + + void setBreakable( bool b ) { breakable = b; } + bool isBreakable() const { return breakable; } + + void setBackgroundColor( const QColor &c ); + QColor *backgroundColor() const { return bgcol; } + void clearBackgroundColor(); + + bool isLineBreak() const { return isBr; } + + void setMovedDown( bool b ) { movedDown = b; } + bool wasMovedDown() const { return movedDown; } + + void setDirection( QChar::Direction d ); + QChar::Direction direction() const; + +protected: + virtual void drawLabel( QPainter* p, int x, int y, int w, int h, int base, const QColorGroup& cg ); + virtual void drawParagString( QPainter &painter, const QString &str, int start, int len, int startX, + int lastY, int baseLine, int bw, int h, bool drawSelections, + QTextStringChar *formatChar, int i, const QMemArray<int> &selectionStarts, + const QMemArray<int> &selectionEnds, const QColorGroup &cg, bool rightToLeft ); + +private: + QMap<int, QTextParagSelection> &selections() const; + QPtrVector<QStyleSheetItem> &styleSheetItemsVec() const; + QPtrList<QTextCustomItem> &floatingItems() const; + + QMap<int, QTextParagLineStart*> lineStarts; + int invalid; + QRect r; + QTextParag *p, *n; + void *docOrPseudo; + uint changed : 1; + uint firstFormat : 1; + uint firstPProcess : 1; + uint needPreProcess : 1; + uint fullWidth : 1; + uint newLinesAllowed : 1; + uint lastInFrame : 1; + uint visible : 1; + uint breakable : 1; + uint isBr : 1; + uint movedDown : 1; + uint mightHaveCustomItems : 1; + uint hasdoc : 1; + int align : 4; + int state, id; + QTextString *str; + QMap<int, QTextParagSelection> *mSelections; + QPtrVector<QStyleSheetItem> *mStyleSheetItemsVec; + QPtrList<QTextCustomItem> *mFloatingItems; + QStyleSheetItem::ListStyle listS; + int numSubParag; + int tm, bm, lm, rm, flm; + QTextFormat *defFormat; + int *tArray; + int tabStopWidth; + QTextParagData *eData; + int list_val; + QColor *bgcol; + +}; + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_EXPORT QTextFormatter +{ +public: + QTextFormatter(); + virtual ~QTextFormatter(); + + virtual int format( QTextDocument *doc, QTextParag *parag, int start, const QMap<int, QTextParagLineStart*> &oldLineStarts ) = 0; + virtual int formatVertically( QTextDocument* doc, QTextParag* parag ); + + bool isWrapEnabled( QTextParag *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 QTextParagLineStart *formatLine( QTextParag *parag, QTextString *string, QTextParagLineStart *line, QTextStringChar *start, + QTextStringChar *last, int align = Qt3::AlignAuto, int space = 0 ); +#ifndef QT_NO_COMPLEXTEXT + virtual QTextParagLineStart *bidiReorderLine( QTextParag *parag, QTextString *string, QTextParagLineStart *line, QTextStringChar *start, + QTextStringChar *last, int align, int space ); +#endif + virtual bool isBreakable( QTextString *string, int pos ) const; + void insertLineStart( QTextParag *parag, int index, QTextParagLineStart *ls ); + + int thisminw; + int thiswused; + +private: + bool wrapEnabled; + int wrapColumn; + bool biw; + +#ifdef HAVE_THAI_BREAKS + static QCString *thaiCache; + static QTextString *cachedString; + static ThBreakIterator *thaiIt; +#endif +}; + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_EXPORT QTextFormatterBreakInWords : public QTextFormatter +{ +public: + QTextFormatterBreakInWords(); + virtual ~QTextFormatterBreakInWords() {} + + int format( QTextDocument *doc, QTextParag *parag, int start, const QMap<int, QTextParagLineStart*> &oldLineStarts ); + +}; + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_EXPORT QTextFormatterBreakWords : public QTextFormatter +{ +public: + QTextFormatterBreakWords(); + virtual ~QTextFormatterBreakWords() {} + + int format( QTextDocument *doc, QTextParag *parag, int start, const QMap<int, QTextParagLineStart*> &oldLineStarts ); + +}; + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_EXPORT QTextIndent +{ +public: + QTextIndent(); + virtual ~QTextIndent() {} + + virtual void indent( QTextDocument *doc, QTextParag *parag, int *oldIndent = 0, int *newIndent = 0 ) = 0; + +}; + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_EXPORT QTextPreProcessor +{ +public: + enum Ids { + Standard = 0 + }; + + QTextPreProcessor(); + virtual ~QTextPreProcessor() {} + + virtual void process( QTextDocument *doc, QTextParag *, int, bool = TRUE ) = 0; + virtual QTextFormat *format( int id ) = 0; + +}; + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Q_EXPORT QTextFormat +{ + friend class QTextFormatCollection; + friend class QTextDocument; + +public: + enum Flags { + NoFlags, + Bold = 1, + Italic = 2, + Underline = 4, + Family = 8, + Size = 16, + Color = 32, + Misspelled = 64, + VAlign = 128, + Font = Bold | Italic | Underline | Family | Size, + Format = Font | Color | Misspelled | VAlign + }; + + enum VerticalAlignment { AlignNormal, AlignSuperScript, AlignSubScript }; + + QTextFormat(); + virtual ~QTextFormat(); + + QTextFormat( const QStyleSheetItem *s ); + QTextFormat( const QFont &f, const QColor &c, QTextFormatCollection *parent = 0 ); + QTextFormat( const QTextFormat &fm ); + QTextFormat makeTextFormat( const QStyleSheetItem *style, const QMap<QString,QString>& attr ) const; + QTextFormat& operator=( const QTextFormat &fm ); + QColor color() const; + QFont font() const; + 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 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 QTextFormat &f ) const; + QTextFormatCollection *parent() const; + QString key() const; + + static QString getKey( const QFont &f, const QColor &c, bool misspelled, VerticalAlignment vAlign ); + + void addRef(); + void removeRef(); + + QString makeFormatChangeTags( QTextFormat *f, const QString& oldAnchorHref, const QString& anchorHref ) const; + QString makeFormatEndTags( const QString& anchorHref ) const; + + static void setPainter( QPainter *p ); + static QPainter* painter(); + void updateStyle(); + void updateStyleFlags(); + void setStyle( const QString &s ); + QString styleName() const { return style; } + + int changed() const { return different; } + bool fontSizesInPixels() { return usePixelSizes; } + +protected: + virtual void generateKey(); + +private: + void update(); + +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; + QTextFormatCollection *collection; + int ref; + QString k; + int logicalFontSize; + int stdSize; + static QPainter *pntr; + QString style; + int different; + +}; + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +#if defined(Q_TEMPLATEDLL) +// MOC_SKIP_BEGIN +template class Q_EXPORT QDict<QTextFormat>; +// MOC_SKIP_END +#endif + +class Q_EXPORT QTextFormatCollection +{ + friend class QTextDocument; + friend class QTextFormat; + +public: + QTextFormatCollection(); + virtual ~QTextFormatCollection(); + + void setDefaultFormat( QTextFormat *f ); + QTextFormat *defaultFormat() const; + virtual QTextFormat *format( QTextFormat *f ); + virtual QTextFormat *format( QTextFormat *of, QTextFormat *nf, int flags ); + virtual QTextFormat *format( const QFont &f, const QColor &c ); + virtual void remove( QTextFormat *f ); + virtual QTextFormat *createFormat( const QTextFormat &f ) { return new QTextFormat( f ); } + virtual QTextFormat *createFormat( const QFont &f, const QColor &c ) { return new QTextFormat( f, c, this ); } + void debug(); + + QStyleSheet *styleSheet() const { return sheet; } + void setStyleSheet( QStyleSheet *s ) { sheet = s; } + void updateStyles(); + void updateFontSizes( int base, bool usePixels ); + void updateFontAttributes( const QFont &f, const QFont &old ); + QDict<QTextFormat> dict() const { return cKey; } + +private: + void updateKeys(); + +private: + QTextFormat *defFormat, *lastFormat, *cachedFormat; + QDict<QTextFormat> cKey; + QTextFormat *cres; + QFont cfont; + QColor ccol; + QString kof, knf; + int cflags; + QStyleSheet *sheet; + +}; + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +inline int QTextString::length() const +{ + return data.size(); +} + +inline void QTextString::operator+=( const QString &s ) +{ + insert( length(), s, 0 ); +} + +inline int QTextParag::length() const +{ + return str->length(); +} + +inline QRect QTextParag::rect() const +{ + return r; +} + +inline QTextParag *QTextCursor::parag() const +{ + return string; +} + +inline int QTextCursor::index() const +{ + return idx; +} + +inline void QTextCursor::setIndex( int i, bool restore ) +{ + if ( restore ) + restoreState(); + if ( i < 0 || i >= string->length() ) { +#if defined(QT_CHECK_RANGE) + qWarning( "QTextCursor::setIndex: %d out of range", i ); +#endif + i = i < 0 ? 0 : string->length() - 1; + } + + tmpIndex = -1; + idx = i; +} + +inline void QTextCursor::setParag( QTextParag *s, bool restore ) +{ + if ( restore ) + restoreState(); + idx = 0; + string = s; + tmpIndex = -1; +} + +inline void QTextCursor::checkIndex() +{ + if ( idx >= string->length() ) + idx = string->length() - 1; +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +inline int QTextDocument::x() const +{ + return cx; +} + +inline int QTextDocument::y() const +{ + return cy; +} + +inline int QTextDocument::width() const +{ + return QMAX( cw, flow_->width() ); +} + +inline int QTextDocument::visibleWidth() const +{ + return vw; +} + +inline QTextParag *QTextDocument::firstParag() const +{ + return fParag; +} + +inline QTextParag *QTextDocument::lastParag() const +{ + return lParag; +} + +inline void QTextDocument::setFirstParag( QTextParag *p ) +{ + fParag = p; +} + +inline void QTextDocument::setLastParag( QTextParag *p ) +{ + lParag = p; +} + +inline void QTextDocument::setWidth( int w ) +{ + cw = QMAX( w, minw ); + flow_->setWidth( cw ); + vw = w; +} + +inline int QTextDocument::minimumWidth() const +{ + return minw; +} + +inline void QTextDocument::setY( int y ) +{ + cy = y; +} + +inline int QTextDocument::leftMargin() const +{ + return leftmargin; +} + +inline void QTextDocument::setLeftMargin( int lm ) +{ + leftmargin = lm; +} + +inline int QTextDocument::rightMargin() const +{ + return rightmargin; +} + +inline void QTextDocument::setRightMargin( int rm ) +{ + rightmargin = rm; +} + +inline QTextPreProcessor *QTextDocument::preProcessor() const +{ + return pProcessor; +} + +inline void QTextDocument::setPreProcessor( QTextPreProcessor * sh ) +{ + pProcessor = sh; +} + +inline void QTextDocument::setFormatter( QTextFormatter *f ) +{ + delete pFormatter; + pFormatter = f; +} + +inline QTextFormatter *QTextDocument::formatter() const +{ + return pFormatter; +} + +inline void QTextDocument::setIndent( QTextIndent *i ) +{ + indenter = i; +} + +inline QTextIndent *QTextDocument::indent() const +{ + return indenter; +} + +inline QColor QTextDocument::selectionColor( int id ) const +{ + return selectionColors[ id ]; +} + +inline bool QTextDocument::invertSelectionText( int id ) const +{ + return selectionText[ id ]; +} + +inline void QTextDocument::setSelectionColor( int id, const QColor &c ) +{ + selectionColors[ id ] = c; +} + +inline void QTextDocument::setInvertSelectionText( int id, bool b ) +{ + selectionText[ id ] = b; +} + +inline QTextFormatCollection *QTextDocument::formatCollection() const +{ + return fCollection; +} + +inline int QTextDocument::alignment() const +{ + return align; +} + +inline void QTextDocument::setAlignment( int a ) +{ + align = a; +} + +inline int *QTextDocument::tabArray() const +{ + return tArray; +} + +inline int QTextDocument::tabStopWidth() const +{ + return tStopWidth; +} + +inline void QTextDocument::setTabArray( int *a ) +{ + tArray = a; +} + +inline void QTextDocument::setTabStops( int tw ) +{ + tStopWidth = tw; +} + +inline QString QTextDocument::originalText() const +{ + if ( oTextValid ) + return oText; + return text(); +} + +inline void QTextDocument::setFlow( QTextFlow *f ) +{ + if ( flow_ ) + delete flow_; + flow_ = f; +} + +inline void QTextDocument::takeFlow() +{ + flow_ = 0; +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +inline QColor QTextFormat::color() const +{ + return col; +} + +inline QFont QTextFormat::font() const +{ + return fn; +} + +inline bool QTextFormat::isMisspelled() const +{ + return missp; +} + +inline QTextFormat::VerticalAlignment QTextFormat::vAlign() const +{ + return ha; +} + +inline bool QTextFormat::operator==( const QTextFormat &f ) const +{ + return k == f.k; +} + +inline QTextFormatCollection *QTextFormat::parent() const +{ + return collection; +} + +inline void QTextFormat::addRef() +{ + ref++; +#ifdef DEBUG_COLLECTION + qDebug( "add ref of '%s' to %d (%p)", k.latin1(), ref, this ); +#endif +} + +inline void QTextFormat::removeRef() +{ + ref--; + if ( !collection ) + return; + if ( this == collection->defFormat ) + return; +#ifdef DEBUG_COLLECTION + qDebug( "remove ref of '%s' to %d (%p)", k.latin1(), ref, this ); +#endif + if ( ref == 0 ) + collection->remove( this ); +} + +inline QString QTextFormat::key() const +{ + return k; +} + +inline bool QTextFormat::useLinkColor() const +{ + return linkColor; +} + +inline void QTextFormat::setStyle( const QString &s ) +{ + style = s; + updateStyleFlags(); +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +inline QTextStringChar &QTextString::at( int i ) const +{ + return data[ i ]; +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +inline QTextStringChar *QTextParag::at( int i ) const +{ + return &str->at( i ); +} + +inline bool QTextParag::isValid() const +{ + return invalid == -1; +} + +inline bool QTextParag::hasChanged() const +{ + return changed; +} + +inline void QTextParag::setBackgroundColor( const QColor & c ) +{ + delete bgcol; + bgcol = new QColor( c ); + setChanged( TRUE ); +} + +inline void QTextParag::clearBackgroundColor() +{ + delete bgcol; bgcol = 0; setChanged( TRUE ); +} + +inline void QTextParag::append( const QString &s, bool reallyAtEnd ) +{ + if ( reallyAtEnd ) + insert( str->length(), s ); + else + insert( QMAX( str->length() - 1, 0 ), s ); +} + +inline QTextParag *QTextParag::prev() const +{ + return p; +} + +inline QTextParag *QTextParag::next() const +{ + return n; +} + +inline bool QTextParag::hasAnySelection() const +{ + return mSelections ? !selections().isEmpty() : FALSE; +} + +inline void QTextParag::setEndState( int s ) +{ + if ( s == state ) + return; + state = s; +} + +inline int QTextParag::endState() const +{ + return state; +} + +inline void QTextParag::setParagId( int i ) +{ + id = i; +} + +inline int QTextParag::paragId() const +{ + if ( id == -1 ) + qWarning( "invalid parag id!!!!!!!! (%p)", (void*)this ); + return id; +} + +inline bool QTextParag::firstPreProcess() const +{ + return firstPProcess; +} + +inline void QTextParag::setFirstPreProcess( bool b ) +{ + firstPProcess = b; +} + +inline QMap<int, QTextParagLineStart*> &QTextParag::lineStartList() +{ + return lineStarts; +} + +inline QTextString *QTextParag::string() const +{ + return str; +} + +inline QTextDocument *QTextParag::document() const +{ + if ( hasdoc ) + return (QTextDocument*) docOrPseudo; + return 0; +} + +inline QTextParagPseudoDocument *QTextParag::pseudoDocument() const +{ + if ( hasdoc ) + return 0; + return (QTextParagPseudoDocument*) docOrPseudo; +} + + +inline QTextTableCell *QTextParag::tableCell() const +{ + return hasdoc ? document()->tableCell () : 0; +} + +inline QTextCommandHistory *QTextParag::commands() const +{ + return hasdoc ? document()->commands() : pseudoDocument()->commandHistory; +} + + +inline void QTextParag::setAlignment( int a ) +{ + if ( a == (int)align ) + return; + align = a; + invalidate( 0 ); +} + +inline void QTextParag::setListStyle( QStyleSheetItem::ListStyle ls ) +{ + listS = ls; + invalidate( 0 ); +} + +inline QStyleSheetItem::ListStyle QTextParag::listStyle() const +{ + return listS; +} + +inline QTextFormat *QTextParag::paragFormat() const +{ + return defFormat; +} + +inline void QTextParag::registerFloatingItem( QTextCustomItem *i ) +{ + floatingItems().append( i ); +} + +inline void QTextParag::unregisterFloatingItem( QTextCustomItem *i ) +{ + floatingItems().removeRef( i ); +} + +inline QBrush *QTextParag::background() const +{ + return tableCell() ? tableCell()->backGround() : 0; +} + +inline int QTextParag::documentWidth() const +{ + return hasdoc ? document()->width() : pseudoDocument()->docRect.width(); +} + +inline int QTextParag::documentVisibleWidth() const +{ + return hasdoc ? document()->visibleWidth() : pseudoDocument()->docRect.width(); +} + +inline int QTextParag::documentX() const +{ + return hasdoc ? document()->x() : pseudoDocument()->docRect.x(); +} + +inline int QTextParag::documentY() const +{ + return hasdoc ? document()->y() : pseudoDocument()->docRect.y(); +} + +inline void QTextParag::setExtraData( QTextParagData *data ) +{ + eData = data; +} + +inline QTextParagData *QTextParag::extraData() const +{ + return eData; +} + +inline void QTextParag::setNewLinesAllowed( bool b ) +{ + newLinesAllowed = b; +} + +inline bool QTextParag::isNewLinesAllowed() const +{ + return newLinesAllowed; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +inline void QTextFormatCollection::setDefaultFormat( QTextFormat *f ) +{ + defFormat = f; +} + +inline QTextFormat *QTextFormatCollection::defaultFormat() const +{ + return defFormat; +} + +// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +inline QTextFormat *QTextStringChar::format() const +{ + return (type == Regular) ? d.format : d.custom->format; +} + + +inline QTextCustomItem *QTextStringChar::customItem() const +{ + return isCustom() ? d.custom->custom : 0; +} + +inline int QTextStringChar::height() const +{ + return !isCustom() ? format()->height() : ( customItem()->placement() == QTextCustomItem::PlaceInline ? customItem()->height : 0 ); +} + +inline int QTextStringChar::ascent() const +{ + return !isCustom() ? format()->ascent() : ( customItem()->placement() == QTextCustomItem::PlaceInline ? customItem()->ascent() : 0 ); +} + +inline int QTextStringChar::descent() const +{ + return !isCustom() ? format()->descent() : 0; +} + +} // namespace Qt3 + +#endif diff --git a/noncore/apps/opie-write/qstylesheet.cpp b/noncore/apps/opie-write/qstylesheet.cpp new file mode 100644 index 0000000..7ab9ec6 --- a/dev/null +++ b/noncore/apps/opie-write/qstylesheet.cpp @@ -0,0 +1,1484 @@ +/**************************************************************************** +** $Id$ +** +** Implementation of the QStyleSheet class +** +** Created : 990101 +** +** Copyright (C) 1992-2000 Trolltech AS. All rights reserved. +** +** This file is part of the kernel module of the Qt GUI Toolkit. +** +** This file may be distributed under the terms of the Q Public License +** as defined by Trolltech AS of Norway and appearing in the file +** LICENSE.QPL included in the packaging of this file. +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition +** licenses may use this file in accordance with the Qt Commercial License +** Agreement provided with the Software. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for +** information about Qt Commercial License Agreements. +** See http://www.trolltech.com/qpl/ for QPL licensing information. +** See http://www.trolltech.com/gpl/ for GPL licensing information. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#include "qstylesheet.h" + +#include "qrichtext_p.h" +#include "qlayout.h" +#include "qpainter.h" +#include "qcleanuphandler.h" + +#include <stdio.h> + +using namespace Qt3; + +namespace Qt3 { + +class QStyleSheetItemData +{ +public: + QStyleSheetItem::DisplayMode disp; + int fontitalic; + int fontunderline; + int fontweight; + int fontsize; + int fontsizelog; + int fontsizestep; + int lineSpacing; + QString fontfamily; + QStyleSheetItem *parentstyle; + QString stylename; + int ncolumns; + QColor col; + bool anchor; + int align; + QStyleSheetItem::VerticalAlignment valign; + int margin[5]; + QStyleSheetItem::ListStyle list; + QStyleSheetItem::WhiteSpaceMode whitespacemode; + QString contxt; + bool selfnest; + QStyleSheet* sheet; +}; + +} + +/*! + \class QStyleSheetItem qstylesheet.h + \ingroup text + \brief The QStyleSheetItem class provides an encapsulation of a set of text styles. + + A style sheet item consists of a name and a set of attributes that + specifiy its font, color, etc. When used in a \link QStyleSheet + style sheet\endlink (see styleSheet()), items define the name() of a + rich text tag and the display property changes associated with it. + + The \link QStyleSheetItem::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 QStyleSheetItem::WhiteSpaceMode white + space mode\endlink; see setWhiteSpaceMode(). An item's margins are + set with setMargin(), and line spacing is set with setLineSpacing(). + 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() and setColor(). +*/ + +/*! \enum QStyleSheetItem::AdditionalStyleValues + \internal +*/ + +/*! \enum QStyleSheetItem::WhiteSpaceMode + + This enum defines the ways in which QStyleSheet can treat whitespace. There are three values at present: + + \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. + +*/ + +/*! \enum QStyleSheetItem::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) +*/ + +/*! + Constructs a new style named \a name for the stylesheet \a parent. + + All properties in QStyleSheetItem are initially in the "do not change" state, + except \link QStyleSheetItem::DisplayMode display mode\endlink, which defaults + to \c DisplayInline. +*/ +QStyleSheetItem::QStyleSheetItem( QStyleSheet* parent, const QString& name ) +{ + d = new QStyleSheetItemData; + d->stylename = name.lower(); + 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. + */ +QStyleSheetItem::QStyleSheetItem( const QStyleSheetItem & other ) +{ + d = new QStyleSheetItemData; + *d = *other.d; +} + + +/*! + Destroys the style. Note that QStyleSheetItem objects become owned + by QStyleSheet when they are created. + */ +QStyleSheetItem::~QStyleSheetItem() +{ + delete d; +} + + + +/*! + Returns the style sheet this item is in. + */ +QStyleSheet* QStyleSheetItem::styleSheet() +{ + return d->sheet; +} + +/*! + \overload + Returns the style sheet this item is in. + */ +const QStyleSheet* QStyleSheetItem::styleSheet() const +{ + return d->sheet; +} + +/*! + \internal + Internal initialization + */ +void QStyleSheetItem::init() +{ + d->disp = DisplayInline; + + d->fontitalic = Undefined; + d->fontunderline = 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 = QStyleSheetItem::ListDisc; + d->whitespacemode = QStyleSheetItem::WhiteSpaceNormal; + d->selfnest = TRUE; + d->lineSpacing = Undefined; +} + +/*! + Returns the name of the style item. +*/ +QString QStyleSheetItem::name() const +{ + return d->stylename; +} + +/*! + Returns the \link QStyleSheetItem::DisplayMode display mode\endlink + of the style. + + \sa setDisplayMode() + */ +QStyleSheetItem::DisplayMode QStyleSheetItem::displayMode() const +{ + return d->disp; +} + +/*! \enum QStyleSheetItem::DisplayMode + + This enum type defines the way adjacent elements are displayed. The possible values are: + + \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. +*/ + +/*! + Sets the display mode of the style to \a m. + + \sa displayMode() + */ +void QStyleSheetItem::setDisplayMode(DisplayMode m) +{ + d->disp=m; +} + + +/*! + Returns the alignment of this style. Possible values are AlignAuto, AlignLeft, + AlignRight, AlignCenter and AlignJustify. + + \sa setAlignment(), Qt::AlignmentFlags + */ +int QStyleSheetItem::alignment() const +{ + return d->align; +} + +/*! + Sets the alignment to \a f. This only makes sense for styles with a + \link QStyleSheetItem::DisplayMode display mode\endlink of + DisplayBlock. Possible values are AlignAuto, AlignLeft, AlignRight, + AlignCenter and AlignJustify. + + \sa alignment(), displayMode(), Qt::AlignmentFlags + */ +void QStyleSheetItem::setAlignment( int f ) +{ + d->align = f; +} + + +/*! + Returns the vertical alignment of the style. Possible values are + VAlignBaseline, VAlignSub and VAlignSuper. + + psa setVerticalAlignment() + */ +QStyleSheetItem::VerticalAlignment QStyleSheetItem::verticalAlignment() const +{ + return d->valign; +} + +/*! \enum QStyleSheetItem::VerticalAlignment + + This enum type defines the way elements are aligned vertically. This + is supported for text elements only. The possible values are: + + \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 and VAlignSuper. + + The vertical alignment property is not inherited. + + \sa verticalAlignment() + */ +void QStyleSheetItem::setVerticalAlignment( VerticalAlignment valign ) +{ + d->valign = valign; +} + + +/*! + Returns TRUE if the style sets an italic font; otherwise returns FALSE. + + \sa setFontItalic(), definesFontItalic() + */ +bool QStyleSheetItem::fontItalic() const +{ + return d->fontitalic > 0; +} + +/*! + If \a italic is TRUE sets italic for the style; otherwise sets + upright. + + \sa fontItalic(), definesFontItalic() + */ +void QStyleSheetItem::setFontItalic(bool italic) +{ + d->fontitalic = italic?1:0; +} + +/*! + Returns whether the style defines a font shape. A style + does not define any shape until setFontItalic() is called. + + \sa setFontItalic(), fontItalic() + */ +bool QStyleSheetItem::definesFontItalic() const +{ + return d->fontitalic != Undefined; +} + +/*! + Returns TRUE if the style sets an underlined font; otherwise returns FALSE. + + \sa setFontUnderline(), definesFontUnderline() + */ +bool QStyleSheetItem::fontUnderline() const +{ + return d->fontunderline > 0; +} + +/*! + If \a underline is TRUE sets underline for the style; otherwise sets + no underline. + + \sa fontUnderline(), definesFontUnderline() + */ +void QStyleSheetItem::setFontUnderline(bool underline) +{ + d->fontunderline = underline?1:0; +} + +/*! + Returns whether the style defines a setting for the underline + property of the font. A style does not define this until + setFontUnderline() is called. + + \sa setFontUnderline(), fontUnderline() */ +bool QStyleSheetItem::definesFontUnderline() const +{ + return d->fontunderline != Undefined; +} + + +/*! + Returns the font weight setting of the style. This is either a + valid QFont::Weight or the value QStyleSheetItem::Undefined. + + \sa setFontWeight(), QFont + */ +int QStyleSheetItem::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 QStyleSheetItem::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 QStyleSheetItem::Undefined. + + \sa setLogicalFontSize(), setLogicalFontSizeStep(), QFont::pointSize(), QFont::setPointSize() + */ +int QStyleSheetItem::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 QStyleSheetItem::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 QStyleSheetItem::logicalFontSizeStep() const +{ + return d->fontsizestep; +} + +/*! + Sets the logical font size step of this style to \a s. + + \sa logicalFontSizeStep() + */ +void QStyleSheetItem::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 QStyleSheetItem::setFontSize(int s) +{ + d->fontsize = s; +} + +/*! + Returns the font size setting of the style. This is either a valid + point size or QStyleSheetItem::Undefined. + + \sa setFontSize(), QFont::pointSize(), QFont::setPointSize() + */ +int QStyleSheetItem::fontSize() const +{ + return d->fontsize; +} + + +/*! + Returns the font family setting of the style. This is either a valid + font family or QString::null if no family has been set. + + \sa setFontFamily(), QFont::family(), QFont::setFamily() + */ +QString QStyleSheetItem::fontFamily() const +{ + return d->fontfamily; +} + +/*! + Sets the font family setting of the style to \a fam. + + \sa fontFamily(), QFont::family(), QFont::setFamily() + */ +void QStyleSheetItem::setFontFamily( const QString& fam) +{ + d->fontfamily = fam; +} + + +/*!\obsolete + Returns the number of columns for this style. + + \sa setNumberOfColumns(), displayMode(), setDisplayMode() + + */ +int QStyleSheetItem::numberOfColumns() const +{ + return d->ncolumns; +} + + +/*!\obsolete + Sets the number of columns for this style. Elements in the style + are divided into columns. + + This makes sense only if the style uses a block display mode + (see QStyleSheetItem::DisplayMode). + + \sa numberOfColumns() + */ +void QStyleSheetItem::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 QStyleSheetItem::color() const +{ + return d->col; +} + +/*! + Sets the text color of this style to \a c. + + \sa color() + */ +void QStyleSheetItem::setColor( const QColor &c) +{ + d->col = c; +} + +/*! + Returns whether this style is an anchor. + + \sa setAnchor() + */ +bool QStyleSheetItem::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 have + connections to other documents or anchors. + + \sa isAnchor() + */ +void QStyleSheetItem::setAnchor(bool anc) +{ + d->anchor = anc; +} + + +/*! + Returns the whitespace mode. + + \sa setWhiteSpaceMode() WhiteSpaceMode + */ +QStyleSheetItem::WhiteSpaceMode QStyleSheetItem::whiteSpaceMode() const +{ + return d->whitespacemode; +} + +/*! + Sets the whitespace mode to \a m. + \sa WhiteSpaceMode + */ +void QStyleSheetItem::setWhiteSpaceMode(WhiteSpaceMode m) +{ + d->whitespacemode = m; +} + + +/*! + Returns the width of margin \a m in pixels. + + The margin, \a m, can be \c MarginLeft, \c MarginRight, + \c MarginTop, \c MarginBottom, \c MarginAll, \c MarginVertical or \c + MarginHorizontal. + + \sa setMargin() Margin + */ +int QStyleSheetItem::margin(Margin m) const +{ + 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, \c MarginAll, \c MarginVertical or \c + MarginHorizontal. The value \a v must be >= 0. + + \sa margin() + */ +void QStyleSheetItem::setMargin(Margin m, int v) +{ + if (m == MarginAll ) { + d->margin[0] = v; + d->margin[1] = v; + d->margin[2] = v; + d->margin[3] = v; + d->margin[4] = 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 + */ +QStyleSheetItem::ListStyle QStyleSheetItem::listStyle() const +{ + return d->list; +} + +/*! \enum QStyleSheetItem::ListStyle + + This enum type defines how the items in a list are prefixed when + displayed. The currently defined values are: + + \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, ... +*/ +/*! + 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 QStyleSheetItem::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 QStyleSheetItem::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 QStyleSheetItem::setContexts( const QString& c) +{ + d->contxt = QChar(' ') + c + QChar(' '); +} + +/*! + Returns TRUE if this style can be nested into an element + of style \a s; otherwise returns FALSE. + + \sa contexts(), setContexts() + */ +bool QStyleSheetItem::allowedInContext( const QStyleSheetItem* s) const +{ + if ( d->contxt.isEmpty() ) + return TRUE; + return d->contxt.find( QChar(' ')+s->name()+QChar(' ')) != -1; +} + + +/*! + Returns TRUE if this style has self-nesting enabled; otherwise + returns FALSE. + + \sa setSelfNesting() + */ +bool QStyleSheetItem::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 QStyleSheetItem::setSelfNesting( bool nesting ) +{ + d->selfnest = nesting; +} + +/*! Sets the linespacing to be \a ls pixels */ + +void QStyleSheetItem::setLineSpacing( int ls ) +{ + d->lineSpacing = ls; +} + +/*! Returns the linespacing */ + +int QStyleSheetItem::lineSpacing() const +{ + return d->lineSpacing; +} + +//************************************************************************ + + + + +//************************************************************************ + + +/*! + \class QStyleSheet qstylesheet.h + \ingroup text + \brief The QStyleSheet class is a collection of styles for rich text + rendering and a generator of tags. + + \ingroup graphics + \ingroup helpsystem + + By creating QStyleSheetItem 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 QTextView 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 QStyleSheet 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 + \list + \i \c{<qt>}...\c{</qt>} + - A Qt rich text document. It understands the following attributes: + \list + \i title + - The caption of the document. This attribute is easily accessible with + QTextView::documentTitle(). + \i 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. The 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 bgcolor + - The background color, for example \c bgcolor="yellow" or \c + bgcolor="#0000FF". + \i background + - The background pixmap, for example \c + background="granit.xpm". The pixmap name will be resolved by + a QMimeSourceFactory(). + \i text + - The default text color, for example \c text="red". + \i link + - The link color, for example \c link="green". + \endlist + \i \c{<h1>...</h1>} + - A top-level heading. + \i \c{<h2>...</h2>} + - A sublevel heading. + \i \c{<h3>...</h3>} + - A sub-sublevel heading. + \i \c{<p>...</p>} + - A left-aligned paragraph. Adjust the alignment with + the \c align attribute. Possible values are + \c left, \c right and \c center. + \i \c{<center>...</center>} + - A centered paragraph. + \i \c{<blockquote>...</blockquote>} + - An indented paragraph that is useful for quotes. + \i \c{<ul>...</ul>} + - 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. + \i \c{<ol>...</ol>} + - 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". + \i <tt><li></tt>...<tt></li></tt> + - A list item. This tag can be used only within the context of + \c ol or \c ul. + \i \c{<pre>...</pre>} + - For larger chunks of code. Whitespaces in the contents are preserved. + For small bits of code use the inline-style \c code. + \endlist + + Anchors and links are done with a single tag: + \list + \i \c{<a>...</a>} + - An anchor or link. The reference target is defined in the \c + href attribute of the tag as in \c{<a + href="target.qml">...</a>}. You can also specify an + additional anchor within the specified target document, for + example \c{<a href="target.qml#123">...</a>}. If \c a is + meant to be an anchor, the reference source is given in the + \c name attribute. + \endlist + + The default character style bindings are + \list + \i \c{<em>...</em>} + - Emphasized. By default this is the same as + \c{<i>...</i>} (italic). + \i \c{<strong>...</strong>} + - Strong. By default this is the same as + \c{<b>...</b>} (bold). + \i \c{<i>...</i>} + - Italic font style. + \i \c{<b>...</b>} + - Bold font style. + \i \c{<u>...</u>} + - Underlined font style. + \i \c{<big>...</big>} + - A larger font size. + \i \c{<small>...</small>} + - A smaller font size. + \i \c{<code>...</code>} + - Indicates code. By default this is the same as + \c{<tt>...</tt>} (typewriter). For + larger junks of code use the block-tag \c pre. + \i \c{<tt>...</tt>} + - Typewriter font style. + \i \c{<font>...</font>} + - Customizes the font size, family and text color. The tag understands + the following attributes: + \list + \i color + - The text color, for example \c color="red" or \c color="#FF0000". + \i 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 face + - The family of the font, for example \c face=times. + \endlist + \endlist + + Special elements are: + \list + \i \c{<img>} + - 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. + \i \c{<hr>} + - A horizonal line. + \i \c{<br>} + - A line break. + \endlist + + Another tag not in any of the above cathegories is + \list + \i \c{<nobr>...</nobr>} + - No break. Prevents word wrap. + \endlist + + 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. + + \list + \i \c{<table>...</table>} + - A table. Tables support the following attributes: + \list + \i bgcolor + - The background color. + \i 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 border + - The width of the table border. The default is 0 (= no border). + \i cellspacing + - Additional space around the table cells. The default is 2. + \i cellpadding + - Additional space around the contents of table cells. The default is 1. + \endlist + \i \c{<tr>...</tr>} + - A table row. This is only valid within a \c table. Rows support + the following attribute: + \list + \i bgcolor + - The background color. + \endlist + \i \c{<th>...</th>} + - A table header cell. Similar to \c td, but defaults to center alignment + and a bold font. + \i \c{<td>...</td>} + - A table data cell. This is only valid within a \c tr. Cells + support the following attributes: + \list + \i bgcolor + - The background color. + \i 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 colspan + - Specifies how many columns this cell spans. The default is 1. + \i rowspan + - Specifies how many rows this cell spans. The default is 1. + \i align + - Alignment; possible values are \c left, \c right, and \c center. The + default is left. + \endlist + \endlist +*/ + +/*! + Creates a style sheet with parent \a parent and name \a name. 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. +*/ +QStyleSheet::QStyleSheet( QObject *parent, const char *name ) + : QObject( parent, name ) +{ + init(); +} + +/*! + Destroys the style sheet. All styles inserted into the style sheet + will be deleted. +*/ +QStyleSheet::~QStyleSheet() +{ +} + +/*! + \internal + Initialized the style sheet to the basic Qt style. +*/ +void QStyleSheet::init() +{ + styles.setAutoDelete( TRUE ); + + nullstyle = new QStyleSheetItem( this, + QString::fromLatin1("") ); + + QStyleSheetItem* style; + + style = new QStyleSheetItem( this, "qml" ); // compatibility + style->setDisplayMode( QStyleSheetItem::DisplayBlock ); + + style = new QStyleSheetItem( this, QString::fromLatin1("qt") ); + style->setDisplayMode( QStyleSheetItem::DisplayBlock ); + //style->setMargin( QStyleSheetItem::MarginAll, 4 ); + + style = new QStyleSheetItem( this, QString::fromLatin1("a") ); + style->setAnchor( TRUE ); + + style = new QStyleSheetItem( this, QString::fromLatin1("em") ); + style->setFontItalic( TRUE ); + + style = new QStyleSheetItem( this, QString::fromLatin1("i") ); + style->setFontItalic( TRUE ); + + style = new QStyleSheetItem( this, QString::fromLatin1("big") ); + style->setLogicalFontSizeStep( 1 ); + style = new QStyleSheetItem( this, QString::fromLatin1("large") ); // compatibility + style->setLogicalFontSizeStep( 1 ); + + style = new QStyleSheetItem( this, QString::fromLatin1("small") ); + style->setLogicalFontSizeStep( -1 ); + + style = new QStyleSheetItem( this, QString::fromLatin1("strong") ); + style->setFontWeight( QFont::Bold); + + style = new QStyleSheetItem( this, QString::fromLatin1("b") ); + style->setFontWeight( QFont::Bold); + + style = new QStyleSheetItem( this, QString::fromLatin1("h1") ); + style->setFontWeight( QFont::Bold); + style->setLogicalFontSize(6); + style->setDisplayMode(QStyleSheetItem::DisplayBlock); + style-> setMargin(QStyleSheetItem::MarginTop, 12); + style-> setMargin(QStyleSheetItem::MarginBottom, 6); + + style = new QStyleSheetItem( this, QString::fromLatin1("h2") ); + style->setFontWeight( QFont::Bold); + style->setLogicalFontSize(5); + style->setDisplayMode(QStyleSheetItem::DisplayBlock); + style-> setMargin(QStyleSheetItem::MarginTop, 10); + style-> setMargin(QStyleSheetItem::MarginBottom, 5); + + style = new QStyleSheetItem( this, QString::fromLatin1("h3") ); + style->setFontWeight( QFont::Bold); + style->setLogicalFontSize(4); + style->setDisplayMode(QStyleSheetItem::DisplayBlock); + style-> setMargin(QStyleSheetItem::MarginTop, 8); + style-> setMargin(QStyleSheetItem::MarginBottom, 4); + + style = new QStyleSheetItem( this, QString::fromLatin1("h4") ); + style->setFontWeight( QFont::Bold); + style->setLogicalFontSize(3); + style->setDisplayMode(QStyleSheetItem::DisplayBlock); + style-> setMargin(QStyleSheetItem::MarginTop, 8); + style-> setMargin(QStyleSheetItem::MarginBottom, 4); + + style = new QStyleSheetItem( this, QString::fromLatin1("h5") ); + style->setFontWeight( QFont::Bold); + style->setLogicalFontSize(2); + style->setDisplayMode(QStyleSheetItem::DisplayBlock); + style-> setMargin(QStyleSheetItem::MarginTop, 8); + style-> setMargin(QStyleSheetItem::MarginBottom, 4); + + style = new QStyleSheetItem( this, QString::fromLatin1("p") ); + style->setDisplayMode(QStyleSheetItem::DisplayBlock); + style-> setMargin(QStyleSheetItem::MarginVertical, 8); + style->setSelfNesting( FALSE ); + + style = new QStyleSheetItem( this, QString::fromLatin1("center") ); + style->setDisplayMode(QStyleSheetItem::DisplayBlock); + style->setAlignment( AlignCenter ); + + style = new QStyleSheetItem( this, QString::fromLatin1("twocolumn") ); + style->setDisplayMode(QStyleSheetItem::DisplayBlock); + style->setNumberOfColumns( 2 ); + + style = new QStyleSheetItem( this, QString::fromLatin1("multicol") ); + style->setDisplayMode(QStyleSheetItem::DisplayBlock); + (void) new QStyleSheetItem( this, QString::fromLatin1("font") ); + + style = new QStyleSheetItem( this, QString::fromLatin1("ul") ); + style->setDisplayMode(QStyleSheetItem::DisplayBlock); + style-> setMargin(QStyleSheetItem::MarginVertical, 4); + + style = new QStyleSheetItem( this, QString::fromLatin1("ol") ); + style->setDisplayMode(QStyleSheetItem::DisplayBlock); + style->setListStyle( QStyleSheetItem::ListDecimal ); + style-> setMargin(QStyleSheetItem::MarginVertical, 4); + + style = new QStyleSheetItem( this, QString::fromLatin1("li") ); + style->setDisplayMode(QStyleSheetItem::DisplayListItem); + style->setSelfNesting( FALSE ); + style->setContexts(QString::fromLatin1("ol ul")); + style-> setMargin(QStyleSheetItem::MarginVertical, 4); + + style = new QStyleSheetItem( this, QString::fromLatin1("code") ); + style->setFontFamily( QString::fromLatin1("courier") ); + + style = new QStyleSheetItem( this, QString::fromLatin1("tt") ); + style->setFontFamily( QString::fromLatin1("courier") ); + + new QStyleSheetItem(this, QString::fromLatin1("img")); + new QStyleSheetItem(this, QString::fromLatin1("br")); + new QStyleSheetItem(this, QString::fromLatin1("hr")); + style = new QStyleSheetItem(this, QString::fromLatin1("sub")); + style->setVerticalAlignment( QStyleSheetItem::VAlignSub ); + style = new QStyleSheetItem(this, QString::fromLatin1("sup")); + style->setVerticalAlignment( QStyleSheetItem::VAlignSuper ); + + style = new QStyleSheetItem( this, QString::fromLatin1("pre") ); + style->setFontFamily( QString::fromLatin1("courier") ); + style->setDisplayMode(QStyleSheetItem::DisplayBlock); + style->setWhiteSpaceMode(QStyleSheetItem::WhiteSpacePre); + + style = new QStyleSheetItem( this, QString::fromLatin1("blockquote") ); + style->setDisplayMode(QStyleSheetItem::DisplayBlock); + style->setMargin(QStyleSheetItem::MarginHorizontal, 40 ); + + style = new QStyleSheetItem( this, QString::fromLatin1("head") ); + style->setDisplayMode(QStyleSheetItem::DisplayNone); + style = new QStyleSheetItem( this, QString::fromLatin1("div") ); + style->setDisplayMode(QStyleSheetItem::DisplayBlock) ; + style = new QStyleSheetItem( this, QString::fromLatin1("dl") ); + style->setDisplayMode(QStyleSheetItem::DisplayBlock); + style = new QStyleSheetItem( this, QString::fromLatin1("dt") ); + style->setDisplayMode(QStyleSheetItem::DisplayBlock); + style->setContexts(QString::fromLatin1("dl") ); + style = new QStyleSheetItem( this, QString::fromLatin1("dd") ); + style->setDisplayMode(QStyleSheetItem::DisplayBlock); + style->setMargin(QStyleSheetItem::MarginLeft, 30); + style->setContexts(QString::fromLatin1("dt dl") ); + style = new QStyleSheetItem( this, QString::fromLatin1("u") ); + style->setFontUnderline( TRUE); + style = new QStyleSheetItem( this, QString::fromLatin1("nobr") ); + style->setWhiteSpaceMode( QStyleSheetItem::WhiteSpaceNoWrap ); + style = new QStyleSheetItem( this, QString::fromLatin1("wsp") ); // qt extension for QTextEdit + style->setWhiteSpaceMode( (QStyleSheetItem::WhiteSpaceMode) 3 ); // WhiteSpaceModeNoCompression + + // tables + style = new QStyleSheetItem( this, QString::fromLatin1("table") ); + style = new QStyleSheetItem( this, QString::fromLatin1("tr") ); + style->setContexts(QString::fromLatin1("table")); + style = new QStyleSheetItem( this, QString::fromLatin1("td") ); + style->setContexts(QString::fromLatin1("tr")); + style = new QStyleSheetItem( this, QString::fromLatin1("th") ); + style->setFontWeight( QFont::Bold ); + style->setAlignment( Qt::AlignCenter ); + style->setContexts(QString::fromLatin1("tr")); + + style = new QStyleSheetItem( this, QString::fromLatin1("html") ); +} + + + +static QStyleSheet* defaultsheet = 0; +static QSingleCleanupHandler<QStyleSheet> 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 serves also as initial + style sheet for the more complex render widgets QTextEdit and + QTextBrowser. + + \sa setDefaultSheet() +*/ +QStyleSheet* QStyleSheet::defaultSheet() +{ + if (!defaultsheet) { + defaultsheet = new QStyleSheet(); + 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 + QStyleSheet. + + \sa defaultSheet() +*/ +void QStyleSheet::setDefaultSheet( QStyleSheet* 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 QStyleSheet::insert( QStyleSheetItem* style ) +{ + styles.insert(style->name(), style); +} + + +/*! + Returns the style with name \a name or 0 if there is no such style. + */ +QStyleSheetItem* QStyleSheet::item( const QString& name) +{ + if ( name.isNull() ) + return 0; + return styles[name]; +} + +/*! + \overload + Returns the style with name \a name or 0 if there is no such style (const version) + */ +const QStyleSheetItem* QStyleSheet::item( const QString& name) const +{ + if ( name.isNull() ) + return 0; + return styles[name]; +} + + +/*! + \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{QMimeSourceFactory::defaultFactory()}) to resolve these references. + The context will then be used to calculate the absolute path. See + QMimeSourceFactory::makeAbsolute() for details. + + \a emptyTag and \a doc are for internal use only. + + This function should not (yet) be used in application code. +*/ +QTextCustomItem* QStyleSheet::tag( const QString& name, + const QMap<QString, QString> &attr, + const QString& context, + const QMimeSourceFactory& factory, + bool /*emptyTag */, QTextDocument *doc ) const +{ + static QString s_img = QString::fromLatin1("img"); + static QString s_hr = QString::fromLatin1("hr"); + + const QStyleSheetItem* style = item( name ); + // first some known tags + if ( !style ) + return 0; + if ( style->name() == s_img ) + return new QTextImage( doc, attr, context, (QMimeSourceFactory&)factory ); + if ( style->name() == s_hr ) + return new QTextHorizontalLine( doc, attr, context, (QMimeSourceFactory&)factory ); + return 0; +} + + +/*! + Auxiliary function. Converts the plain text string \a plain to a + rich text formatted paragraph while preserving its look. + + \a mode defines the whitespace mode. Possible values are \c + QStyleSheetItem::WhiteSpacePre (no wrapping, all whitespaces + preserved) and \c QStyleSheetItem::WhiteSpaceNormal (wrapping, + simplified whitespaces). + + \sa escape() + */ +QString QStyleSheet::convertFromPlainText( const QString& plain, QStyleSheetItem::WhiteSpaceMode mode ) +{ + int col = 0; + QString rich; + rich += "<p>"; + for ( int i = 0; i < int(plain.length()); ++i ) { + if ( plain[i] == '\n' ){ + if ( col == 1 ) + rich += "<p></p>"; + else + rich += "<br>"; + col = 0; + } + else if ( mode == QStyleSheetItem::WhiteSpacePre && plain[i] == '\t' ){ + rich += 0x00a0U; + while ( col % 4 ) { + rich += 0x00a0U; + ++col; + } + } + else if ( mode == QStyleSheetItem::WhiteSpacePre && plain[i].isSpace() ) + rich += 0x00a0U; + else if ( plain[i] == '<' ) + rich +="<"; + else if ( plain[i] == '>' ) + rich +=">"; + else if ( plain[i] == '&' ) + rich +="&"; + else + rich += plain[i]; + ++col; + } + rich += "</p>"; + return rich; +} + +/*! + Auxiliary function. Converts the plain text string \a plain to a + rich text formatted string with any HTML meta-characters escaped. + + \sa convertFromPlainText() + */ +QString QStyleSheet::escape( const QString& plain) +{ + QString rich; + for ( int i = 0; i < int(plain.length()); ++i ) { + if ( plain[i] == '<' ) + rich +="<"; + else if ( plain[i] == '>' ) + rich +=">"; + else if ( plain[i] == '&' ) + rich +="&"; + else + rich += plain[i]; + } + return rich; +} + +// Must doc this enum somewhere, and it is logically related to QStyleSheet + +/*! + \enum Qt::TextFormat + + This enum is used in widgets that can display both plain text and + rich text, e.g. QLabel. It is used for deciding whether a text + string should be interpreted as one or the other. This is + normally done by passing one of the enum values to a setTextFormat() + function. + + \value PlainText The text string is interpreted as a plain text string. + + \value RichText The text string is interpreted as a rich text string + using the current QStyleSheet::defaultSheet(). + + \value AutoText The text string is interpreted as for \c RichText if + QStyleSheet::mightBeRichText() returns TRUE, otherwise as for \c + PlainText. +*/ + +/*! + Returns TRUE if the string \a text is likely to be rich text; + otherwise returns FALSE. + + Note: The 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 + most common cases, there is no guarantee. +*/ +bool QStyleSheet::mightBeRichText( const QString& text) +{ + if ( text.isEmpty() ) + return FALSE; + if ( text.left(5).lower() == "<!doc" ) + return TRUE; + int open = 0; + while ( open < int(text.length()) && text[open] != '<' + && text[open] != '\n' && text[open] != '&') + ++open; + if ( text[open] == '&' ) { + if ( text.mid(open+1,3) == "lt;" ) + return TRUE; // support desperate attempt of user to see <...> + } else if ( text[open] == '<' ) { + int close = text.find('>', open); + if ( close > -1 ) { + QString tag; + for (int i = open+1; i < close; ++i) { + if ( text[i].isDigit() || text[i].isLetter() ) + tag += text[i]; + else if ( !tag.isEmpty() && text[i].isSpace() ) + break; + else if ( !text[i].isSpace() && (!tag.isEmpty() || text[i] != '!' ) ) + return FALSE; // that's not a tag + } + return defaultSheet()->item( tag.lower() ) != 0; + } + } + return FALSE; +} + + +/*! \fn void QStyleSheet::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 QStyleSheet::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 QStyleSheetItem::logicalFontSize(), + QStyleSheetItem::logicalFontSizeStep(), QFont::setPointSize() + */ +void QStyleSheet::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 = baseSize/2; + 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( s ); + else + font.setPointSize( s ); +} diff --git a/noncore/apps/opie-write/qstylesheet.h b/noncore/apps/opie-write/qstylesheet.h new file mode 100644 index 0000000..bb209fa --- a/dev/null +++ b/noncore/apps/opie-write/qstylesheet.h @@ -0,0 +1,221 @@ +/**************************************************************************** +** $Id$ +** +** Definition of the QStyleSheet class +** +** Created : 990101 +** +** Copyright (C) 1992-2000 Trolltech AS. All rights reserved. +** +** This file is part of the kernel module of the Qt GUI Toolkit. +** +** This file may be distributed under the terms of the Q Public License +** as defined by Trolltech AS of Norway and appearing in the file +** LICENSE.QPL included in the packaging of this file. +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition +** licenses may use this file in accordance with the Qt Commercial License +** Agreement provided with the Software. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for +** information about Qt Commercial License Agreements. +** See http://www.trolltech.com/qpl/ for QPL licensing information. +** See http://www.trolltech.com/gpl/ for GPL licensing information. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#ifndef QSTYLESHEET_H +#define QSTYLESHEET_H + +#ifndef QT_H +#include "qt3namespace.h" +#include "qstring.h" +#include "qvaluelist.h" +#include "qvector.h" +#include "qdict.h" +#include "qobject.h" +#endif // QT_H + +template<class Key, class T> class QMap; + +namespace Qt3 { + +class QStyleSheet; +class QTextDocument; +class QStyleSheetItemData; + +class Q_EXPORT QStyleSheetItem : public Qt +{ +public: + QStyleSheetItem( QStyleSheet* parent, const QString& name ); + QStyleSheetItem( const QStyleSheetItem & ); + ~QStyleSheetItem(); + + QString name() const; + + QStyleSheet* styleSheet(); + const QStyleSheet* styleSheet() const; + + enum AdditionalStyleValues { Undefined = - 1}; + + enum DisplayMode { + DisplayBlock, + DisplayInline, + DisplayListItem, + DisplayNone + }; + + 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 isAnchor() const; + void setAnchor(bool anc); + + enum WhiteSpaceMode { WhiteSpaceNormal, WhiteSpacePre, WhiteSpaceNoWrap }; + WhiteSpaceMode whiteSpaceMode() const; + void setWhiteSpaceMode(WhiteSpaceMode m); + + enum Margin { + MarginLeft, + MarginRight, + MarginTop, + MarginBottom, + MarginFirstLine, + MarginAll, + MarginVertical, + MarginHorizontal + }; + + int margin( Margin m) const; + void setMargin( Margin, int); + + enum ListStyle { + ListDisc, + ListCircle, + ListSquare, + ListDecimal, + ListLowerAlpha, + ListUpperAlpha + }; + + ListStyle listStyle() const; + void setListStyle( ListStyle ); + + QString contexts() const; + void setContexts( const QString& ); + bool allowedInContext( const QStyleSheetItem* ) const; + + bool selfNesting() const; + void setSelfNesting( bool ); + + void setLineSpacing( int ls ); + int lineSpacing() const; + +private: + void init(); + QStyleSheetItemData* d; +}; + + +#if defined(Q_TEMPLATEDLL) +// MOC_SKIP_BEGIN +template class Q_EXPORT QDict<QStyleSheetItem>; +template class Q_EXPORT QValueList< QPtrVector<QStyleSheetItem> >; +template class Q_EXPORT QPtrVector<QStyleSheetItem>; +template class Q_EXPORT QValueList<QStyleSheetItem::ListStyle>; +// MOC_SKIP_END +#endif + +class QTextCustomItem; + +class Q_EXPORT QStyleSheet : public QObject +{ + Q_OBJECT +public: + QStyleSheet( QObject *parent=0, const char *name=0 ); + virtual ~QStyleSheet(); + + static QStyleSheet* defaultSheet(); + static void setDefaultSheet( QStyleSheet* ); + + + QStyleSheetItem* item( const QString& name); + const QStyleSheetItem* item( const QString& name) const; + + void insert( QStyleSheetItem* item); + + virtual QTextCustomItem* tag( const QString& name, + const QMap<QString, QString> &attr, + const QString& context, + const QMimeSourceFactory& factory, + bool emptyTag, QTextDocument *doc ) const; + + static QString escape( const QString& ); + static QString convertFromPlainText( const QString&, QStyleSheetItem::WhiteSpaceMode mode = QStyleSheetItem::WhiteSpacePre ); + static bool mightBeRichText( const QString& ); + + virtual void scaleFont( QFont& font, int logicalSize ) const; + + virtual void error( const QString& ) const; + +private: + void init(); + QDict<QStyleSheetItem> styles; + QStyleSheetItem* nullstyle; +}; + +} // namespace Qt3 + +#endif // QSTYLESHEET_H diff --git a/noncore/apps/opie-write/qt3namespace.h b/noncore/apps/opie-write/qt3namespace.h new file mode 100644 index 0000000..81c5020 --- a/dev/null +++ b/noncore/apps/opie-write/qt3namespace.h @@ -0,0 +1,28 @@ +#ifndef QT3NAMESPACE_H +#define QT3NAMESPACE_H + +#include <qnamespace.h> + +#define Q_ASSERT ASSERT +#define Q_WS_QWS + +#define QMemArray QArray +#define QPtrList QList +#define QPtrListIterator QListIterator +#define QPtrVector QVector + +namespace Qt3 { + +enum NewAlignmentFlags { + AlignAuto = 0x0000, + AlignJustify = 0x0080, + AlignHorizontal_Mask = Qt::AlignLeft | Qt::AlignRight | Qt::AlignHCenter | AlignJustify +}; + +enum NewWidgetFlags { + WStaticContents = Qt::WNorthWestGravity +}; + +} + +#endif // QT3NAMESPACE_H diff --git a/noncore/apps/opie-write/qtextedit.cpp b/noncore/apps/opie-write/qtextedit.cpp new file mode 100644 index 0000000..9c5ea79 --- a/dev/null +++ b/noncore/apps/opie-write/qtextedit.cpp @@ -0,0 +1,4516 @@ +/**************************************************************************** +** $Id$ +** +** Implementation of the QTextEdit class +** +** Created : 990101 +** +** Copyright (C) 1992-2000 Trolltech AS. All rights reserved. +** +** This file is part of the widgets module of the Qt GUI Toolkit. +** +** This file may be distributed under the terms of the Q Public License +** as defined by Trolltech AS of Norway and appearing in the file +** LICENSE.QPL included in the packaging of this file. +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition +** licenses may use this file in accordance with the Qt Commercial License +** Agreement provided with the Software. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for +** information about Qt Commercial License Agreements. +** See http://www.trolltech.com/qpl/ for QPL licensing information. +** See http://www.trolltech.com/gpl/ for GPL licensing information. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#include "qtextedit.h" + +#include "qrichtext_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 "qlistbox.h" +#include "qvbox.h" +#include "qapplication.h" +#include "qclipboard.h" +#include "qcolordialog.h" +#include "qfontdialog.h" +#include "qstylesheet.h" +#include "qdragobject.h" +#include "qurl.h" +#include "qcursor.h" +#include "qregexp.h" +#include "qpopupmenu.h" + +#define ACCEL_KEY(k) "\t" + QString("Ctrl+" #k) + +using namespace Qt3; + +struct QUndoRedoInfoPrivate +{ + QTextString text; +}; + +namespace Qt3 { + +class QTextEditPrivate +{ +public: + QTextEditPrivate() + :preeditStart(-1),preeditLength(-1),ensureCursorVisibleInShowEvent(FALSE) {} + int id[ 7 ]; + int preeditStart; + int preeditLength; + bool ensureCursorVisibleInShowEvent; +}; + +} + +static bool block_set_alignment = FALSE; + +/*! + \class QTextEdit qtextedit.h + \brief The QTextEdit widget provides a sophisticated single-page rich text editor. + + \ingroup basic + \ingroup text + \mainclass + + QTextEdit is an advanced WYSIWYG editor supporting rich text + formatting. It is optimized to handle large documents and to + respond quickly to user input. + + QTextEdit works on paragraphs and characters. A paragraph is a + formatted string which is word-wrapped to fit into the width of + the widget. 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. + + QTextEdit can display images (using QMimeSourceFactory), lists and + tables. If the text is too large to view within the text edit's + viewport, scrollbars 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(). Change the style sheet with \l{setStyleSheet()}; see + QStyleSheet for details. The images identified by image tags are + displayed if they can be interpreted using the text edit's + \l{QMimeSourceFactory}; 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 QTextEdit, 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 QStyleSheet 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. + + The text edit documentation uses the following concepts: + \list + \i <i>current format</i> -- + this is the format at the current cursor position, \e and it + is the format of the selected text if any. + \i <i>current paragraph</i> -- the paragraph which contains the + cursor. + \endlist + + The text is set or replaced using setText() which deletes any + existing text and replaces it with the text passed in the + setText() call. Text can be inserted with insert(), paste() and + pasteSubType(). 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). + + The current format's attributes are set with setItalic(), + setBold(), setUnderline(), setFamily() (font family), + setPointSize(), setColor() and setCurrentFont(). The current + paragraph's style is set with setParagType() and its 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(). QTextEdit 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. + + QTextEdit 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. + + The indent() function is used to reindent a paragraph. It is + useful for code editors, for example in <em>Qt Designer</em>'s + code editor \e{Ctrl+I} invokes the indent() function. + + Loading and saving text is achieved using setText() and text(), + for example: + \code + QFile file( fileName ); // Read the text from a file + if ( file.open( IO_ReadOnly ) ) { + QTextStream ts( &file ); + textEdit->setText( ts.read() ); + } + \endcode + \code + QFile file( fileName ); // Write the text to a file + if ( file.open( IO_WriteOnly ) ) { + QTextStream ts( &file ); + ts << textEdit->text(); + textEdit->setModified( FALSE ); + } + \endcode + + 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. + + The list of key-bindings which are implemented for editing: + \table + \header \i Keypresses \i Action + \row \i \e{Backspace} \i Delete the character to the left of the cursor + \row \i \e{Delete} \i Delete the character to the right of the cursor + \row \i \e{Ctrl+A} \i Move the cursor to the beginning of the line + \row \i \e{Ctrl+B} \i Move the cursor one character left + \row \i \e{Ctrl+C} \i Copy the marked text to the clipboard (also + \e{Ctrl+Insert} under Windows) + \row \i \e{Ctrl+D} \i Delete the character to the right of the cursor + \row \i \e{Ctrl+E} \i Move the cursor to the end of the line + \row \i \e{Ctrl+F} \i Move the cursor one character right + \row \i \e{Ctrl+H} \i Delete the character to the left of the cursor + \row \i \e{Ctrl+K} \i Delete to end of line + \row \i \e{Ctrl+N} \i Move the cursor one line down + \row \i \e{Ctrl+P} \i Move the cursor one line up + \row \i \e{Ctrl+V} \i Paste the clipboard text into line edit + (also \e{Shift+Insert} under Windows) + \row \i \e{Ctrl+X} \i Cut the marked text, copy to clipboard + (also \e{Shift+Delete} under Windows) + \row \i \e{Ctrl+Z} \i Undo the last operation + \row \i \e{Ctrl+Y} \i Redo the last operation + \row \i \e{LeftArrow} \i Move the cursor one character left + \row \i \e{Ctrl+LeftArrow} \i Move the cursor one word left + \row \i \e{RightArrow} \i Move the cursor one character right + \row \i \e{Ctrl+RightArrow} \i Move the cursor one word right + \row \i \e{UpArrow} \i Move the cursor one line up + \row \i \e{Ctrl+UpArrow} \i Move the cursor one word up + \row \i \e{DownArrow} \i Move the cursor one line down + \row \i \e{Ctrl+Down Arrow} \i Move the cursor one word down + \row \i \e{PageUp} \i Move the cursor one page up + \row \i \e{PageDown} \i Move the cursor one page down + \row \i \e{Home} \i Move the cursor to the beginning of the line + \row \i \e{Ctrl+Home} \i Move the cursor to the beginning of the text + \row \i \e{End} \i Move the cursor to the end of the line + \row \i \e{Ctrl+End} \i Move the cursor to the end of the text + \row \i \e{Shift+Wheel} \i Scroll the page horizontally + (the Wheel is the mouse wheel) + \row \i \e{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, <i>Shift+Right Arrow</i> + will select the character to the right, and <i>Shift+Ctrl+Right + Arrow</i> 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(). + + QTextEdit can also be used as read-only text viewer. Call + setReadOnly( TRUE ) to disable editing. A read-only QTextEdit + provides the same functionality as the (obsolete) QTextView. + (QTextView is still supplied for compatibility with old code.) + + When QTextEdit 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 \e{UpArrow} \i Move one line up + \row \i \e{DownArrow} \i Move one line down + \row \i \e{LeftArrow} \i Move one character left + \row \i \e{RightArrow} \i Move one character right + \row \i \e{PageUp} \i Move one (viewport) page up + \row \i \e{PageDown} \i Move one (viewport) page down + \row \i \e{Home} \i Move to the beginning of the text + \row \i \e{End} \i Move to the end of the text + \row \i \e{Shift+Wheel} \i Scroll the page horizontally (the Wheel is the mouse wheel) + \row \i \e{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 QMimeSourceFactory uses to resolve + the locations of files and images. It is passed to the + mimeSourceFactory() when quering data. (See QTextEdit() and + \l{context()}.) + + 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. +*/ + +/*! \enum QTextEdit::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. +*/ + +/*! \enum QTextEdit::VerticalAlignment + + This enum is used to set the vertical alignment of the text. + + \value AlignNormal Normal alignment + \value AlignSuperScript Superscript + \value AlignSubScript Subscript +*/ + +/*! \fn void QTextEdit::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 QTextEdit::textChanged() + + This signal is emitted whenever the text in the text edit changes. + + \sa setText() append() + */ + +/*! \fn void QTextEdit::selectionChanged() + + This signal is emitted whenever the selection changes. + + \sa setSelection() copyAvailable() +*/ + +/*! \fn QTextDocument *QTextEdit::document() const + + \internal + + This function returns the QTextDocument which is used by the text + edit. +*/ + +/*! \fn void QTextEdit::setDocument( QTextDocument *doc ) + + \internal + + This function sets the QTextDocument 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 + QTextDocument 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 QTextEdit::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 page up + + \value MovePgDown Moves the cursor one page down +*/ + + +/*! + \property QTextEdit::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 QTextEdit::setCurrentFont( const QFont &f ) + + Sets the font of the current format to \a f. + + \sa font() setPointSize() setFamily() +*/ + +/*! + \property QTextEdit::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 QTextEdit::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 QTextEdit::modificationChanged( bool m ) + + This signal is emitted when the modification of the document + changed. If \a m is TRUE, the document was modified, otherwise the + modification state has been reset to unmodified. + + \sa modified +*/ + +/*! \fn void QTextEdit::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 QTextEdit::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 QTextEdit::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 QTextEdit::currentVerticalAlignmentChanged( VerticalAlignment a ) + + This signal is emitted if the vertical alignment of the current + format has changed. + + The new vertical alignment is \a a. + + \sa setVerticalAlignment() +*/ + +/*! \fn void QTextEdit::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 QTextEdit::cursorPositionChanged( QTextCursor *c ) + + This signal is emitted if the position of the cursor changed. \a c + points to the text cursor object. + + \sa setCursorPosition() +*/ + +/*! \overload void QTextEdit::cursorPositionChanged( int para, int pos ) + + This signal is emitted if the position of the cursor changed. \a + para contains the paragraph index and \a pos contains the character + position within the paragraph. + + \sa setCursorPosition() +*/ + +/*! \fn void QTextEdit::returnPressed() + + This signal is emitted if the user pressed the Return or the Enter key. +*/ + +/*! + \fn QTextCursor *QTextEdit::textCursor() const + + Returns the text edit's text cursor. + + \warning QTextCursor is not in the public API, but in special + circumstances you might wish to use it. +*/ + +/*! Constructs an empty QTextEdit with parent \a parent and name \a + name. +*/ + +QTextEdit::QTextEdit( QWidget *parent, const char *name ) + : QScrollView( parent, name, WStaticContents | WRepaintNoErase | WResizeNoErase ), + doc( new QTextDocument( 0 ) ), undoRedoInfo( doc ) +{ + init(); +} + +/*! + Constructs a QTextEdit with parent \a parent and name \a name. The + text edit will display the text \a text using context \a context. + + The \a context is a path which the text edit's QMimeSourceFactory + 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 + QMimeSourceFactory 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 + QMimeSourceFactory 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". + +*/ + +QTextEdit::QTextEdit( const QString& text, const QString& context, + QWidget *parent, const char *name) + : QScrollView( parent, name, WStaticContents | WRepaintNoErase | WResizeNoErase ), + doc( new QTextDocument( 0 ) ), undoRedoInfo( doc ) +{ + init(); + setText( text, context ); +} + +/*! \reimp */ + +QTextEdit::~QTextEdit() +{ + delete undoRedoInfo.d; + undoRedoInfo.d = 0; + delete cursor; + delete doc; + delete d; +} + +void QTextEdit::init() +{ + setFrameStyle( Sunken ); + undoEnabled = TRUE; + readonly = TRUE; + setReadOnly( FALSE ); + d = new QTextEditPrivate; + connect( doc, SIGNAL( minimumWidthChanged( int ) ), + this, SLOT( documentWidthChanged( int ) ) ); + + mousePressed = FALSE; + inDoubleClick = FALSE; + modified = FALSE; + onLink = QString::null; + overWrite = FALSE; + wrapMode = WidgetWidth; + wrapWidth = -1; + wPolicy = AtWhiteSpace; + inDnD = FALSE; + + doc->setFormatter( new QTextFormatterBreakWords ); + currentFormat = doc->formatCollection()->defaultFormat(); + currentAlignment = Qt3::AlignAuto; + + viewport()->setBackgroundMode( PaletteBase ); + viewport()->setAcceptDrops( TRUE ); + resizeContents( 0, doc->lastParag() ? + ( doc->lastParag()->paragId() + 1 ) * doc->formatCollection()->defaultFormat()->height() : 0 ); + + setKeyCompression( TRUE ); + viewport()->setMouseTracking( TRUE ); +#ifndef QT_NO_CURSOR + viewport()->setCursor( isReadOnly() ? arrowCursor : ibeamCursor ); +#endif + cursor = new QTextCursor( doc ); + + formatTimer = new QTimer( this ); + connect( formatTimer, SIGNAL( timeout() ), + this, SLOT( formatMore() ) ); + lastFormatted = doc->firstParag(); + + 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 + + + formatMore(); + + blinkCursorVisible = FALSE; + + viewport()->setFocusProxy( this ); + viewport()->setFocusPolicy( WheelFocus ); + viewport()->installEventFilter( this ); + installEventFilter( this ); +} + +void QTextEdit::paintDocument( bool drawAll, QPainter *p, int cx, int cy, int cw, int ch ) +{ + bool drawCur = hasFocus() || viewport()->hasFocus(); + if ( hasSelectedText() || isReadOnly() || !cursorVisible ) + drawCur = FALSE; + QColorGroup g = colorGroup(); + if ( doc->paper() ) + g.setBrush( QColorGroup::Base, *doc->paper() ); + + if ( contentsY() < doc->y() ) { + p->fillRect( contentsX(), contentsY(), visibleWidth(), doc->y(), + g.brush( QColorGroup::Base ) ); + } + if ( drawAll && doc->width() - contentsX() < cx + cw ) { + p->fillRect( doc->width() - contentsX(), cy, cx + cw - doc->width() + contentsX(), ch, + g.brush( QColorGroup::Base ) ); + } + + p->setBrushOrigin( -contentsX(), -contentsY() ); + + lastFormatted = doc->draw( p, cx, cy, cw, ch, g, !drawAll, drawCur, cursor ); + + if ( lastFormatted == doc->lastParag() ) + resizeContents( contentsWidth(), doc->height() ); + + if ( contentsHeight() < visibleHeight() && ( !doc->lastParag() || doc->lastParag()->isValid() ) && drawAll ) + p->fillRect( 0, contentsHeight(), visibleWidth(), + visibleHeight() - contentsHeight(), g.brush( QColorGroup::Base ) ); +} + +/*! \reimp */ + +void QTextEdit::drawContents( QPainter *p, int cx, int cy, int cw, int ch ) +{ + paintDocument( TRUE, p, cx, cy, cw, ch ); + int v; + p->setPen( foregroundColor() ); + 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; + } + } + +} + +/*! \reimp */ + +void QTextEdit::drawContents( QPainter * ) +{ +} + +/*! \reimp */ + +bool QTextEdit::event( QEvent *e ) +{ + if ( e->type() == QEvent::AccelOverride && !isReadOnly() ) { + QKeyEvent* ke = (QKeyEvent*) e; + if ( ke->state() == NoButton || ke->state() == Keypad ) { + if ( ke->key() < Key_Escape ) { + ke->accept(); + } else { + switch ( ke->key() ) { + case Key_Return: + case Key_Enter: + case Key_Delete: + case Key_Home: + case Key_End: + case Key_Backspace: + ke->accept(); + default: + break; + } + } + } else if ( ke->state() & ControlButton ) { + switch ( ke->key() ) { +// Those are too frequently used for application functionality +/* case Key_A: + case Key_B: + case Key_D: + case Key_E: + case Key_F: + case Key_H: + case Key_I: + case Key_K: + case Key_N: + case Key_P: + case Key_T: +*/ + case Key_C: + case Key_V: + case Key_X: + case Key_Y: + case Key_Z: + case Key_Left: + case Key_Right: + case Key_Up: + case Key_Down: + case Key_Home: + case Key_End: + case Key_Tab: +#if defined (Q_WS_WIN) + case Key_Insert: + case Key_Delete: +#endif + ke->accept(); + default: + break; + } + } else { + switch ( ke->key() ) { +#if defined (Q_WS_WIN) + case Key_Insert: + ke->accept(); +#endif + default: + break; + } + } + } + + if ( e->type() == QEvent::Show && d->ensureCursorVisibleInShowEvent ) { + sync(); + ensureCursorVisible(); + d->ensureCursorVisibleInShowEvent = FALSE; + } + return QWidget::event( e ); +} + +/*! + Processes the key event, \a e. + By default key events are used to provide keyboard navigation and + text editing. +*/ + +void QTextEdit::keyPressEvent( QKeyEvent *e ) +{ + changeIntervalTimer->stop(); + interval = 10; + bool unknown = FALSE; + if ( isReadOnly() ) { + if ( !handleReadOnlyKeyEvent( e ) ) + QScrollView::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->parag()->document()->nextDoubleBuffered = TRUE; + repaintChanged(); + } + + bool clearUndoRedoInfo = TRUE; + + + switch ( e->key() ) { + case Key_Left: + case 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->parag()->string()->isRightToLeft() == (e->key() == Key_Right) ) + a = e->state() & ControlButton ? MoveWordBackward : MoveBackward; + else + a = e->state() & ControlButton ? MoveWordForward : MoveForward; + moveCursor( a, e->state() & ShiftButton ); + break; + } + case Key_Up: + moveCursor( e->state() & ControlButton ? MovePgUp : MoveUp, e->state() & ShiftButton ); + break; + case Key_Down: + moveCursor( e->state() & ControlButton ? MovePgDown : MoveDown, e->state() & ShiftButton ); + break; + case Key_Home: + moveCursor( e->state() & ControlButton ? MoveHome : MoveLineStart, e->state() & ShiftButton ); + break; + case Key_End: + moveCursor( e->state() & ControlButton ? MoveEnd : MoveLineEnd, e->state() & ShiftButton ); + break; + case Key_Prior: + moveCursor( MovePgUp, e->state() & ShiftButton ); + break; + case Key_Next: + moveCursor( MovePgDown, e->state() & ShiftButton ); + break; + case Key_Return: case Key_Enter: + if ( doc->hasSelection( QTextDocument::Standard, FALSE ) ) + removeSelectedText(); +#ifndef QT_NO_CURSOR + viewport()->setCursor( isReadOnly() ? arrowCursor : ibeamCursor ); +#endif + clearUndoRedoInfo = FALSE; + doKeyboardAction( ActionReturn ); + emit returnPressed(); + break; + case Key_Delete: +#if defined (Q_WS_WIN) + if ( e->state() & ShiftButton ) { + cut(); + break; + } else +#endif + if ( doc->hasSelection( QTextDocument::Standard, TRUE ) ) { + removeSelectedText(); + break; + } + doKeyboardAction( ActionDelete ); + clearUndoRedoInfo = FALSE; + + break; + case Key_Insert: + if ( e->state() & ShiftButton ) + paste(); + break; + case Key_Backspace: + if ( doc->hasSelection( QTextDocument::Standard, TRUE ) ) { + removeSelectedText(); + break; + } + + if ( !cursor->parag()->prev() && + cursor->atParagStart() ) + break; + + doKeyboardAction( ActionBackspace ); + clearUndoRedoInfo = FALSE; + + break; + case Key_F16: // Copy key on Sun keyboards + copy(); + break; + case Key_F18: // Paste key on Sun keyboards + paste(); + break; + case Key_F20: // Cut key on Sun keyboards + cut(); + break; + default: { + if ( e->text().length() && + ( !( e->state() & ControlButton ) && + !( e->state() & AltButton ) || + ( ( e->state() & ControlButton | AltButton ) == (ControlButton|AltButton) ) ) && + ( !e->ascii() || e->ascii() >= 32 || e->text() == "\t" ) ) { + clearUndoRedoInfo = FALSE; + if ( e->key() == Key_Tab ) { + if ( textFormat() == Qt::RichText && + cursor->index() == 0 && cursor->parag()->style() && + cursor->parag()->style()->displayMode() == + QStyleSheetItem::DisplayListItem ) { + cursor->parag()->incDepth(); + drawCursor( FALSE ); + repaintChanged(); + drawCursor( TRUE ); + break; + } + } + if ( textFormat() == Qt::RichText && ( !cursor->parag()->style() || + cursor->parag()->style()->displayMode() == QStyleSheetItem::DisplayBlock ) && + cursor->index() == 0 && ( e->text()[0] == '-' || e->text()[0] == '*' ) ) { + setParagType( QStyleSheetItem::DisplayListItem, QStyleSheetItem::ListDisc ); + cursor->parag()->incDepth(); + drawCursor( FALSE ); + repaintChanged(); + drawCursor( TRUE ); + } else { + if ( overWrite && !cursor->atParagEnd() ) + cursor->remove(); + QString t = e->text(); + QTextParag *p = cursor->parag(); + if ( p && p->string() && p->string()->isRightToLeft() ) { + QChar *c = (QChar *)t.unicode(); + int l = t.length(); + while( l-- ) { + if ( c->mirrored() ) + *c = c->mirroredChar(); + c++; + } + } + insert( t, TRUE, FALSE, TRUE ); + } + break; + } else if ( e->state() & ControlButton ) { + switch ( e->key() ) { + case Key_C: case Key_F16: // Copy key on Sun keyboards + copy(); + break; + case Key_V: + paste(); + break; + case Key_X: + cut(); + break; + case Key_I: case Key_T: case Key_Tab: + indent(); + break; + case Key_A: +#if defined(Q_WS_X11) + moveCursor( MoveLineStart, e->state() & ShiftButton ); +#else + selectAll( TRUE ); +#endif + break; + case Key_B: + moveCursor( MoveBackward, e->state() & ShiftButton ); + break; + case Key_F: + moveCursor( MoveForward, e->state() & ShiftButton ); + break; + case Key_D: + if ( doc->hasSelection( QTextDocument::Standard ) ) { + removeSelectedText(); + break; + } + doKeyboardAction( ActionDelete ); + clearUndoRedoInfo = FALSE; + break; + case Key_H: + if ( doc->hasSelection( QTextDocument::Standard ) ) { + removeSelectedText(); + break; + } + if ( !cursor->parag()->prev() && + cursor->atParagStart() ) + break; + + doKeyboardAction( ActionBackspace ); + clearUndoRedoInfo = FALSE; + break; + case Key_E: + moveCursor( MoveLineEnd, e->state() & ShiftButton ); + break; + case Key_N: + moveCursor( MoveDown, e->state() & ShiftButton ); + break; + case Key_P: + moveCursor( MoveUp, e->state() & ShiftButton ); + break; + case Key_Z: + undo(); + break; + case Key_Y: + redo(); + break; + case Key_K: + doKeyboardAction( ActionKill ); + break; +#if defined(Q_WS_WIN) + case Key_Insert: + copy(); + break; + case Key_Delete: + del(); + break; +#endif + default: + unknown = FALSE; + break; + } + } else { + unknown = TRUE; + } + } + } + + emit cursorPositionChanged( cursor ); + emit cursorPositionChanged( cursor->parag()->paragId(), cursor->index() ); + if ( clearUndoRedoInfo ) + clearUndoRedo(); + changeIntervalTimer->start( 100, TRUE ); + if ( unknown ) + e->ignore(); +} + +/*! + Executes keyboard action \a action. This is normally called by + a key event handler. +*/ + +void QTextEdit::doKeyboardAction( KeyboardAction action ) +{ + if ( isReadOnly() ) + return; + + if ( cursor->nestedDepth() != 0 ) // #### for 3.0, disable editing of tables as this is not advanced enough + return; + + lastFormatted = cursor->parag(); + drawCursor( FALSE ); + bool doUpdateCurrentFormat = TRUE; + + switch ( action ) { + case ActionDelete: { + checkUndoRedoInfo( UndoRedoInfo::Delete ); + if ( !undoRedoInfo.valid() ) { + undoRedoInfo.id = cursor->parag()->paragId(); + undoRedoInfo.index = cursor->index(); + undoRedoInfo.d->text = QString::null; + } + undoRedoInfo.d->text += cursor->parag()->at( cursor->index() )->c; + if ( cursor->parag()->at( cursor->index() )->format() ) { + cursor->parag()->at( cursor->index() )->format()->addRef(); + undoRedoInfo.d->text.at( undoRedoInfo.d->text.length() - 1 ).setFormat( cursor->parag()->at( cursor->index() )->format() ); + } + QTextParag *old = cursor->parag(); + if ( cursor->remove() ) { + if ( old != cursor->parag() && lastFormatted == old ) + lastFormatted = cursor->parag() ? cursor->parag()->prev() : 0; + undoRedoInfo.d->text += "\n"; + } + } break; + case ActionBackspace: + if ( textFormat() == Qt::RichText && + cursor->parag()->style() && + cursor->parag()->style()->displayMode() == QStyleSheetItem::DisplayListItem && + cursor->index() == 0 ) { + cursor->parag()->decDepth(); + lastFormatted = cursor->parag(); + repaintChanged(); + drawCursor( TRUE ); + return; + } + checkUndoRedoInfo( UndoRedoInfo::Delete ); + if ( !undoRedoInfo.valid() ) { + undoRedoInfo.id = cursor->parag()->paragId(); + undoRedoInfo.index = cursor->index(); + undoRedoInfo.d->text = QString::null; + } + cursor->gotoPreviousLetter(); + undoRedoInfo.d->text.prepend( QString( cursor->parag()->at( cursor->index() )->c ) ); + if ( cursor->parag()->at( cursor->index() )->format() ) { + cursor->parag()->at( cursor->index() )->format()->addRef(); + undoRedoInfo.d->text.at( 0 ).setFormat( cursor->parag()->at( cursor->index() )->format() ); + } + undoRedoInfo.index = cursor->index(); + if ( cursor->remove() ) { + undoRedoInfo.d->text.remove( 0, 1 ); + undoRedoInfo.d->text.prepend( "\n" ); + undoRedoInfo.index = cursor->index(); + undoRedoInfo.id = cursor->parag()->paragId(); + } + lastFormatted = cursor->parag(); + break; + case ActionReturn: { + checkUndoRedoInfo( UndoRedoInfo::Return ); + if ( !undoRedoInfo.valid() ) { + undoRedoInfo.id = cursor->parag()->paragId(); + undoRedoInfo.index = cursor->index(); + undoRedoInfo.d->text = QString::null; + } + undoRedoInfo.d->text += "\n"; + cursor->splitAndInsertEmptyParag(); + if ( cursor->parag()->prev() ) { + lastFormatted = cursor->parag()->prev(); + lastFormatted->invalidate( 0 ); + } + doUpdateCurrentFormat = FALSE; + } break; + case ActionKill: + checkUndoRedoInfo( UndoRedoInfo::Delete ); + if ( !undoRedoInfo.valid() ) { + undoRedoInfo.id = cursor->parag()->paragId(); + undoRedoInfo.index = cursor->index(); + undoRedoInfo.d->text = QString::null; + } + if ( cursor->atParagEnd() ) { + undoRedoInfo.d->text += cursor->parag()->at( cursor->index() )->c; + if ( cursor->parag()->at( cursor->index() )->format() ) { + cursor->parag()->at( cursor->index() )->format()->addRef(); + undoRedoInfo.d->text.at( undoRedoInfo.d->text.length() - 1 ).setFormat( cursor->parag()->at( cursor->index() )->format() ); + } + QTextParag *old = cursor->parag(); + if ( cursor->remove() ) { + if ( old != cursor->parag() && lastFormatted == old ) + lastFormatted = cursor->parag() ? cursor->parag()->prev() : 0; + undoRedoInfo.d->text += "\n"; + } + } else { + int oldLen = undoRedoInfo.d->text.length(); + undoRedoInfo.d->text += cursor->parag()->string()->toString().mid( cursor->index() ); + for ( int i = cursor->index(); i < cursor->parag()->length(); ++i ) { + if ( cursor->parag()->at( i )->format() ) { + cursor->parag()->at( i )->format()->addRef(); + undoRedoInfo.d->text.at( oldLen + i - cursor->index() ).setFormat( cursor->parag()->at( i )->format() ); + } + } + undoRedoInfo.d->text.remove( undoRedoInfo.d->text.length() - 1, 1 ); + cursor->killLine(); + } + break; + } + + formatMore(); + repaintChanged(); + ensureCursorVisible(); + drawCursor( TRUE ); + + if ( hasFocus() || viewport()->hasFocus() ) { + int h = cursor->parag()->lineHeightOfChar( cursor->index() ); + if ( !readonly ) { + QFont f = cursor->parag()->at( cursor->index() )->format()->font(); + setMicroFocusHint( cursor->x() - contentsX() + frameWidth(), + cursor->y() + cursor->parag()->rect().y() - contentsY() + frameWidth(), 0, h, TRUE ); + } + } + + if ( doUpdateCurrentFormat ) + updateCurrentFormat(); + setModified(); + emit textChanged(); +} + +void QTextEdit::readFormats( QTextCursor &c1, QTextCursor &c2, int oldLen, QTextString &text, bool fillStyles ) +{ + c2.restoreState(); + c1.restoreState(); + if ( c1.parag() == c2.parag() ) { + for ( int i = c1.index(); i < c2.index(); ++i ) { + if ( c1.parag()->at( i )->format() ) { + c1.parag()->at( i )->format()->addRef(); + text.at( oldLen + i - c1.index() ).setFormat( c1.parag()->at( i )->format() ); + } + } + if ( fillStyles ) { + undoRedoInfo.oldAligns[ 0 ] = c1.parag()->alignment(); + undoRedoInfo.oldStyles << c1.parag()->styleSheetItems(); + undoRedoInfo.oldListStyles << c1.parag()->listStyle(); + } + } else { + int lastIndex = oldLen; + int i; + for ( i = c1.index(); i < c1.parag()->length(); ++i ) { + if ( c1.parag()->at( i )->format() ) { + c1.parag()->at( i )->format()->addRef(); + text.at( lastIndex ).setFormat( c1.parag()->at( i )->format() ); + lastIndex++; + } + } + QTextParag *p = c1.parag()->next(); + while ( p && p != c2.parag() ) { + for ( int i = 0; i < p->length(); ++i ) { + if ( p->at( i )->format() ) { + p->at( i )->format()->addRef(); + text.at( i + lastIndex ).setFormat( p->at( i )->format() ); + } + } + lastIndex += p->length(); + p = p->next(); + } + for ( i = 0; i < c2.index(); ++i ) { + if ( c2.parag()->at( i )->format() ) { + c2.parag()->at( i )->format()->addRef(); + text.at( i + lastIndex ).setFormat( c2.parag()->at( i )->format() ); + } + } + if ( fillStyles ) { + QTextParag *p = c1.parag(); + i = 0; + while ( p ) { + if ( i < (int)undoRedoInfo.oldAligns.size() ) + undoRedoInfo.oldAligns[ i ] = p->alignment(); + undoRedoInfo.oldStyles << p->styleSheetItems(); + undoRedoInfo.oldListStyles << p->listStyle(); + if ( p == c2.parag() ) + break; + p = p->next(); + ++i; + } + } + } +} + +/*! Removes the selection \a selNum (by default 0). This does not + remove the selected text. + + \sa removeSelectedText() +*/ + +void QTextEdit::removeSelection( int selNum ) +{ + doc->removeSelection( selNum ); + repaintChanged(); +} + +/*! Deletes the selected text (i.e. the default selection's text) of + the selection \a selNum (by default, 0). If there is no selected text + nothing happens. + + \sa selectedText removeSelection() +*/ + +void QTextEdit::removeSelectedText( int selNum ) +{ + if ( isReadOnly() ) + return; + + QTextCursor c1 = doc->selectionStartCursor( selNum ); + QTextCursor c2 = doc->selectionEndCursor( selNum ); + + // ### no support for editing tables yet + if ( c1.nestedDepth() || c2.nestedDepth() ) + return; + + for ( int i = 0; i < (int)doc->numSelections(); ++i ) { + if ( i == selNum ) + continue; + doc->removeSelection( i ); + } + + drawCursor( FALSE ); + checkUndoRedoInfo( UndoRedoInfo::RemoveSelected ); + if ( !undoRedoInfo.valid() ) { + doc->selectionStart( selNum, undoRedoInfo.id, undoRedoInfo.index ); + undoRedoInfo.d->text = QString::null; + } + int oldLen = undoRedoInfo.d->text.length(); + undoRedoInfo.d->text = doc->selectedText( selNum, FALSE ); + undoRedoInfo.oldAligns.resize( undoRedoInfo.oldAligns.size() + QMAX( 0, c2.parag()->paragId() - c1.parag()->paragId() + 1 ) ); + readFormats( c1, c2, oldLen, undoRedoInfo.d->text, TRUE ); + doc->removeSelectedText( selNum, cursor ); + if ( cursor->isValid() ) { + ensureCursorVisible(); + lastFormatted = cursor->parag(); + formatMore(); + repaintChanged(); + 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(), TRUE ); +#endif +#ifndef QT_NO_CURSOR + viewport()->setCursor( isReadOnly() ? arrowCursor : ibeamCursor ); +#endif + if ( hasFocus() || viewport()->hasFocus() ) { + int h = cursor->parag()->lineHeightOfChar( cursor->index() ); + if ( !readonly ) { + QFont f = cursor->parag()->at( cursor->index() )->format()->font(); + setMicroFocusHint( cursor->x() - contentsX() + frameWidth(), + cursor->y() + cursor->parag()->rect().y() - contentsY() + frameWidth(), 0, h, TRUE ); + } + } + } else { + cursor->setDocument( doc ); + cursor->setParag( doc->firstParag() ); + cursor->setIndex( 0 ); + drawCursor( TRUE ); + viewport()->repaint( TRUE ); + } + setModified(); + emit textChanged(); + emit selectionChanged(); +} + +/*! 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 QTextEdit::moveCursor( CursorAction action, bool select ) +{ + drawCursor( FALSE ); + if ( select ) { + if ( !doc->hasSelection( QTextDocument::Standard ) ) + doc->setSelectionStart( QTextDocument::Standard, cursor ); + moveCursor( action ); + if ( doc->setSelectionEnd( QTextDocument::Standard, cursor ) ) { + cursor->parag()->document()->nextDoubleBuffered = TRUE; + repaintChanged(); + } else { + drawCursor( TRUE ); + } + ensureCursorVisible(); + emit selectionChanged(); + emit copyAvailable( doc->hasSelection( QTextDocument::Standard ) ); + } else { + bool redraw = doc->removeSelection( QTextDocument::Standard ); + moveCursor( action ); + if ( !redraw ) { + ensureCursorVisible(); + drawCursor( TRUE ); + } else { + cursor->parag()->document()->nextDoubleBuffered = TRUE; + repaintChanged(); + ensureCursorVisible(); + drawCursor( TRUE ); +#ifndef QT_NO_CURSOR + viewport()->setCursor( isReadOnly() ? arrowCursor : ibeamCursor ); +#endif + } + if ( redraw ) { + emit copyAvailable( doc->hasSelection( QTextDocument::Standard ) ); + emit selectionChanged(); + } + } + + drawCursor( TRUE ); + updateCurrentFormat(); + if ( hasFocus() || viewport()->hasFocus() ) { + int h = cursor->parag()->lineHeightOfChar( cursor->index() ); + if ( !readonly ) { + QFont f = cursor->parag()->at( cursor->index() )->format()->font(); + setMicroFocusHint( cursor->x() - contentsX() + frameWidth(), + cursor->y() + cursor->parag()->rect().y() - contentsY() + frameWidth(), 0, h, TRUE ); + } + } +} + +/*! \overload +*/ + +void QTextEdit::moveCursor( CursorAction action ) +{ + 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->lastParag() ); + cursor->gotoEnd(); + break; + } + + if ( hasFocus() || viewport()->hasFocus() ) { + int h = cursor->parag()->lineHeightOfChar( cursor->index() ); + if ( !readonly ) { + QFont f = cursor->parag()->at( cursor->index() )->format()->font(); + setMicroFocusHint( cursor->x() - contentsX() + frameWidth(), + cursor->y() + cursor->parag()->rect().y() - contentsY() + frameWidth(), 0, h, TRUE ); + } + } + updateCurrentFormat(); +} + +/*! \reimp */ + +void QTextEdit::resizeEvent( QResizeEvent *e ) +{ + QScrollView::resizeEvent( e ); +} + +/*! \reimp */ + +void QTextEdit::viewportResizeEvent( QResizeEvent *e ) +{ + QScrollView::viewportResizeEvent( e ); + if ( e->oldSize().width() != e->size().width() ) + doResize(); +} + +static bool blockEnsureCursorVisible = FALSE; + +/*! + Ensures that the cursor is visible by scrolling the text edit if + necessary. + + \sa setCursorPosition() +*/ + +void QTextEdit::ensureCursorVisible() +{ + if ( blockEnsureCursorVisible ) + return; + if ( !isVisible() ) { + d->ensureCursorVisibleInShowEvent = TRUE; + return; + } + lastFormatted = cursor->parag(); + formatMore(); + QTextStringChar *chr = cursor->parag()->at( cursor->index() ); + int h = cursor->parag()->lineHeightOfChar( cursor->index() ); + int x = cursor->parag()->rect().x() + chr->x + cursor->offsetX(); + int y = 0; int dummy; + cursor->parag()->lineHeightOfChar( cursor->index(), &dummy, &y ); + y += cursor->parag()->rect().y() + cursor->offsetY(); + int w = 1; + ensureVisible( x, y + h / 2, w, h / 2 + 2 ); +} + +/*! + \internal +*/ +void QTextEdit::drawCursor( bool visible ) +{ + if ( !isUpdatesEnabled() || + !viewport()->isUpdatesEnabled() || + !cursor->parag() || + !cursor->parag()->isValid() || + !selectedText().isEmpty() || + ( visible && !hasFocus() && !viewport()->hasFocus() && !inDnD ) || + isReadOnly() ) + return; + + QPainter p( viewport() ); + QRect r( cursor->topParag()->rect() ); + cursor->parag()->setChanged( TRUE ); + p.translate( -contentsX() + cursor->totalOffsetX(), -contentsY() + cursor->totalOffsetY() ); + QPixmap *pix = 0; + QColorGroup cg( colorGroup() ); + if ( cursor->parag()->background() ) + cg.setBrush( QColorGroup::Base, *cursor->parag()->background() ); + else if ( doc->paper() ) + cg.setBrush( QColorGroup::Base, *doc->paper() ); + p.setBrushOrigin( -contentsX(), -contentsY() ); + cursor->parag()->document()->nextDoubleBuffered = TRUE; + if ( !cursor->nestedDepth() ) { + int h = cursor->parag()->lineHeightOfChar( cursor->index() ); + int dist = 5; + if ( ( cursor->parag()->alignment() & Qt3::AlignJustify ) == Qt3::AlignJustify ) + dist = 50; + int x = r.x() - cursor->totalOffsetX() + cursor->x() - dist; + x = QMAX( x, 0 ); + p.setClipRect( QRect( x - contentsX(), + r.y() - cursor->totalOffsetY() + cursor->y() - contentsY(), 2 * dist, h ) ); + doc->drawParag( &p, cursor->parag(), x, + r.y() - cursor->totalOffsetY() + cursor->y(), 2 * dist, h, pix, cg, visible, cursor ); + } else { + doc->drawParag( &p, cursor->parag(), r.x() - cursor->totalOffsetX(), + r.y() - cursor->totalOffsetY(), r.width(), r.height(), + pix, cg, visible, cursor ); + } + cursorVisible = visible; +} + +enum { + IdUndo = 0, + IdRedo = 1, + IdCut = 2, + IdCopy = 3, + IdPaste = 4, + IdClear = 5, + IdSelectAll = 6 +}; + +/*! \reimp */ +#ifndef QT_NO_WHEELEVENT +void QTextEdit::contentsWheelEvent( QWheelEvent *e ) +{ + if ( isReadOnly() ) { + if ( e->state() & ControlButton ) { + if ( e->delta() > 0 ) + zoomOut(); + else if ( e->delta() < 0 ) + zoomIn(); + return; + } + } + QScrollView::contentsWheelEvent( e ); +} +#endif + +/*! \reimp */ + +void QTextEdit::contentsMousePressEvent( QMouseEvent *e ) +{ + clearUndoRedo(); + QTextCursor oldCursor = *cursor; + QTextCursor c = *cursor; + mousePos = e->pos(); + mightStartDrag = FALSE; + pressedLink = QString::null; + + if ( e->button() == LeftButton ) { + mousePressed = TRUE; + drawCursor( FALSE ); + placeCursor( e->pos() ); + ensureCursorVisible(); + + if ( isReadOnly() && linksEnabled() ) { + QTextCursor c = *cursor; + placeCursor( e->pos(), &c, TRUE ); + if ( c.parag() && c.parag()->at( c.index() ) && + c.parag()->at( c.index() )->isAnchor() ) { + pressedLink = c.parag()->at( c.index() )->anchorHref(); + } + } + +#ifndef QT_NO_DRAGANDDROP + if ( doc->inSelection( QTextDocument::Standard, e->pos() ) ) { + mightStartDrag = TRUE; + drawCursor( TRUE ); + dragStartTimer->start( QApplication::startDragTime(), TRUE ); + dragStartPos = e->pos(); + return; + } +#endif + + bool redraw = FALSE; + if ( doc->hasSelection( QTextDocument::Standard ) ) { + if ( !( e->state() & ShiftButton ) ) { + redraw = doc->removeSelection( QTextDocument::Standard ); + doc->setSelectionStart( QTextDocument::Standard, cursor ); + } else { + redraw = doc->setSelectionEnd( QTextDocument::Standard, cursor ) || redraw; + } + } else { + if ( isReadOnly() || !( e->state() & ShiftButton ) ) { + doc->setSelectionStart( QTextDocument::Standard, cursor ); + } else { + doc->setSelectionStart( QTextDocument::Standard, &c ); + redraw = doc->setSelectionEnd( QTextDocument::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() ? arrowCursor : ibeamCursor ); +#endif + } + } else if ( e->button() == MidButton ) { + bool redraw = doc->removeSelection( QTextDocument::Standard ); + if ( !redraw ) { + drawCursor( TRUE ); + } else { + repaintChanged(); +#ifndef QT_NO_CURSOR + viewport()->setCursor( isReadOnly() ? arrowCursor : ibeamCursor ); +#endif + } + } + + if ( *cursor != oldCursor ) + updateCurrentFormat(); +} + +/*! \reimp */ + +void QTextEdit::contentsMouseMoveEvent( QMouseEvent *e ) +{ + 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( ibeamCursor ); +#endif + return; + } +#endif + mousePos = e->pos(); + handleMouseMove( mousePos ); + oldMousePos = mousePos; + } + +#ifndef QT_NO_CURSOR + if ( !isReadOnly() && !mousePressed ) { + if ( doc->hasSelection( QTextDocument::Standard ) && doc->inSelection( QTextDocument::Standard, e->pos() ) ) + viewport()->setCursor( arrowCursor ); + else + viewport()->setCursor( ibeamCursor ); + } +#endif + updateCursor( e->pos() ); +} + +/*! \reimp */ + +void QTextEdit::contentsMouseReleaseEvent( QMouseEvent * e ) +{ + QTextCursor 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; + } + emit cursorPositionChanged( cursor ); + emit cursorPositionChanged( cursor->parag()->paragId(), cursor->index() ); + if ( oldCursor != *cursor ) + updateCurrentFormat(); + inDoubleClick = FALSE; + +#ifndef QT_NO_NETWORKPROTOCOL + if ( !onLink.isEmpty() && onLink == pressedLink && linksEnabled() ) { + QUrl u( doc->context(), onLink, TRUE ); + emitLinkClicked( u.toString( FALSE, FALSE ) ); + + // 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( QTextDocument::Standard, TRUE ) ) + doc->removeSelection( QTextDocument::Standard ); + + emit copyAvailable( doc->hasSelection( QTextDocument::Standard ) ); + emit selectionChanged(); +} + +/*! \reimp */ + +void QTextEdit::contentsMouseDoubleClickEvent( QMouseEvent * ) +{ + QTextCursor c1 = *cursor; + QTextCursor c2 = *cursor; + if ( cursor->index() > 0 && !cursor->parag()->at( cursor->index()-1 )->c.isSpace() ) + c1.gotoPreviousWord(); + if ( !cursor->parag()->at( cursor->index() )->c.isSpace() && !cursor->atParagEnd() ) + c2.gotoNextWord(); + + doc->setSelectionStart( QTextDocument::Standard, &c1 ); + doc->setSelectionEnd( QTextDocument::Standard, &c2 ); + + *cursor = c2; + + repaintChanged(); + + inDoubleClick = TRUE; + mousePressed = TRUE; +} + +#ifndef QT_NO_DRAGANDDROP + +/*! \reimp */ + +void QTextEdit::contentsDragEnterEvent( QDragEnterEvent *e ) +{ + if ( isReadOnly() || !QTextDrag::canDecode( e ) ) { + e->ignore(); + return; + } + e->acceptAction(); + inDnD = TRUE; +} + +/*! \reimp */ + +void QTextEdit::contentsDragMoveEvent( QDragMoveEvent *e ) +{ + if ( isReadOnly() || !QTextDrag::canDecode( e ) ) { + e->ignore(); + return; + } + drawCursor( FALSE ); + placeCursor( e->pos(), cursor ); + drawCursor( TRUE ); + e->acceptAction(); +} + +/*! \reimp */ + +void QTextEdit::contentsDragLeaveEvent( QDragLeaveEvent * ) +{ + inDnD = FALSE; +} + +/*! \reimp */ + +void QTextEdit::contentsDropEvent( QDropEvent *e ) +{ + if ( isReadOnly() ) + return; + inDnD = FALSE; + e->acceptAction(); + QString text; + bool intern = FALSE; + if ( QTextDrag::decode( e, text ) ) { + if ( ( e->source() == this || + e->source() == viewport() ) && + e->action() == QDropEvent::Move ) { + removeSelectedText(); + intern = TRUE; + } else { + doc->removeSelection( QTextDocument::Standard ); +#ifndef QT_NO_CURSOR + viewport()->setCursor( isReadOnly() ? arrowCursor : ibeamCursor ); +#endif + } + drawCursor( FALSE ); + placeCursor( e->pos(), cursor ); + drawCursor( TRUE ); + if ( !cursor->nestedDepth() ) { + insert( text, FALSE, TRUE, FALSE ); + } else { + if ( intern ) + undo(); + e->ignore(); + } + } +} + +#endif + +void QTextEdit::autoScrollTimerDone() +{ + if ( mousePressed ) + handleMouseMove( viewportToContents( viewport()->mapFromGlobal( QCursor::pos() ) ) ); +} + +void QTextEdit::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 ); + QTextCursor oldCursor = *cursor; + + placeCursor( pos ); + + if ( inDoubleClick ) { + QTextCursor cl = *cursor; + cl.gotoPreviousWord(); + QTextCursor cr = *cursor; + cr.gotoNextWord(); + + int diff = QABS( oldCursor.parag()->at( oldCursor.index() )->x - mousePos.x() ); + int ldiff = QABS( cl.parag()->at( cl.index() )->x - mousePos.x() ); + int rdiff = QABS( cr.parag()->at( cr.index() )->x - mousePos.x() ); + + + if ( cursor->parag()->lineStartOfChar( cursor->index() ) != + oldCursor.parag()->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( QTextDocument::Standard ) ) { + redraw = doc->setSelectionEnd( QTextDocument::Standard, cursor ) || redraw; + } + + if ( !redraw ) { + drawCursor( TRUE ); + } else { + repaintChanged(); + drawCursor( TRUE ); + } + + if ( currentFormat && currentFormat->key() != cursor->parag()->at( cursor->index() )->format()->key() ) { + currentFormat->removeRef(); + currentFormat = doc->formatCollection()->format( cursor->parag()->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->parag()->alignment() ) { + currentAlignment = cursor->parag()->alignment(); + block_set_alignment = TRUE; + emit currentAlignmentChanged( currentAlignment ); + block_set_alignment = FALSE; + } +} + +/*! + \fn void QTextEdit::placeCursor( const QPoint &pos, QTextCursor *c ) + 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 QTextEdit::placeCursor( const QPoint &pos, QTextCursor *c, bool link ) +{ + if ( !c ) + c = cursor; + + c->restoreState(); + QTextParag *s = doc->firstParag(); + c->place( pos, s, link ); + if ( hasFocus() || viewport()->hasFocus() ) { + int h = cursor->parag()->lineHeightOfChar( cursor->index() ); + if ( !readonly ) { + QFont f = cursor->parag()->at( cursor->index() )->format()->font(); + setMicroFocusHint( cursor->x() - contentsX() + frameWidth(), + cursor->y() + cursor->parag()->rect().y() - contentsY() + frameWidth(), 0, h, TRUE ); + } + } +} + +void QTextEdit::formatMore() +{ + if ( !lastFormatted ) + return; + + int bottom = contentsHeight(); + int lastBottom = -1; + int to = !sender() ? 2 : 20; + bool firstVisible = FALSE; + QRect cr( contentsX(), contentsY(), visibleWidth(), visibleHeight() ); + for ( int i = 0; ( i < to || firstVisible ) && lastFormatted; ++i ) { + lastFormatted->format(); + if ( i == 0 ) + firstVisible = lastFormatted->rect().intersects( cr ); + else if ( firstVisible ) + firstVisible = lastFormatted->rect().intersects( cr ); + bottom = QMAX( bottom, lastFormatted->rect().top() + + lastFormatted->rect().height() ); + lastBottom = lastFormatted->rect().top() + lastFormatted->rect().height(); + lastFormatted = lastFormatted->next(); + if ( lastFormatted ) + lastBottom = -1; + } + + if ( bottom > contentsHeight() ) + resizeContents( contentsWidth(), QMAX( doc->height(), bottom ) ); + else if ( lastBottom != -1 && lastBottom < contentsHeight() ) + resizeContents( contentsWidth(), QMAX( doc->height(), lastBottom ) ); + + if ( lastFormatted ) + formatTimer->start( interval, TRUE ); + else + interval = QMAX( 0, interval ); +} + +void QTextEdit::doResize() +{ + if ( wrapMode == FixedPixelWidth ) + return; + doc->setMinimumWidth( -1 ); + resizeContents( 0, 0 ); + doc->setWidth( visibleWidth() ); + doc->invalidate(); + lastFormatted = doc->firstParag(); + interval = 0; + formatMore(); + repaintContents( contentsX(), contentsY(), visibleWidth(), visibleHeight(), FALSE ); +} + +/*! \internal */ + +void QTextEdit::doChangeInterval() +{ + interval = 0; +} + +/*! \reimp */ + +bool QTextEdit::eventFilter( QObject *o, QEvent *e ) +{ + if ( o == this || o == viewport() ) { + if ( e->type() == QEvent::FocusIn ) { + blinkTimer->start( QApplication::cursorFlashTime() / 2 ); + drawCursor( TRUE ); + + if ( !readonly ) { + // make sure the micro focus hint is updated... + QFont f = cursor->parag()->at( cursor->index() )->format()->font(); + setMicroFocusHint( cursor->x() - contentsX() + frameWidth(), + cursor->y() + cursor->parag()->rect().y() - + contentsY() + frameWidth(), 0, + cursor->parag()->lineHeightOfChar( cursor->index() ), + TRUE ); + } + } else if ( e->type() == QEvent::FocusOut ) { + blinkTimer->stop(); + drawCursor( FALSE ); + } + } + + return QScrollView::eventFilter( o, e ); +} + +/*! + Inserts \a text at the current cursor position. If \a indent is TRUE, + the paragraph is re-indented. If \a checkNewLine is TRUE, newline + characters in \a text result in hard line breaks (i.e. new + paragraphs). If \a checkNewLine is FALSE the behaviour of the editor + is undefined if the \a text contains newlines. If \a removeSelected is + TRUE, any selected text (in selection 0) is removed before the text is + inserted. + + \sa paste() pasteSubType() +*/ + +void QTextEdit::insert( const QString &text, bool indent, bool checkNewLine, bool removeSelected ) +{ + if ( cursor->nestedDepth() != 0 ) // #### for 3.0, disable editing of tables as this is not advanced enough + return; + QString txt( text ); + drawCursor( FALSE ); + if ( !isReadOnly() && doc->hasSelection( QTextDocument::Standard ) && removeSelected ) + removeSelectedText(); + QTextCursor c2 = *cursor; + int oldLen = 0; + + if ( undoEnabled && !isReadOnly() ) { + checkUndoRedoInfo( UndoRedoInfo::Insert ); + if ( !undoRedoInfo.valid() ) { + undoRedoInfo.id = cursor->parag()->paragId(); + undoRedoInfo.index = cursor->index(); + undoRedoInfo.d->text = QString::null; + } + oldLen = undoRedoInfo.d->text.length(); + } + + lastFormatted = checkNewLine && cursor->parag()->prev() ? + cursor->parag()->prev() : cursor->parag(); + QTextCursor oldCursor = *cursor; + cursor->insert( txt, checkNewLine ); + if ( doc->useFormatCollection() ) { + doc->setSelectionStart( QTextDocument::Temp, &oldCursor ); + doc->setSelectionEnd( QTextDocument::Temp, cursor ); + doc->setFormat( QTextDocument::Temp, currentFormat, QTextFormat::Format ); + doc->removeSelection( QTextDocument::Temp ); + } + + if ( indent && ( txt == "{" || txt == "}" || txt == ":" || txt == "#" ) ) + cursor->indent(); + formatMore(); + repaintChanged(); + ensureCursorVisible(); + drawCursor( TRUE ); + + if ( undoEnabled && !isReadOnly() ) { + undoRedoInfo.d->text += txt; + if ( !doc->preProcessor() ) { + for ( int i = 0; i < (int)txt.length(); ++i ) { + if ( txt[ i ] != '\n' && c2.parag()->at( c2.index() )->format() ) { + c2.parag()->at( c2.index() )->format()->addRef(); + undoRedoInfo.d->text.setFormat( oldLen + i, c2.parag()->at( c2.index() )->format(), TRUE ); + } + c2.gotoNextLetter(); + } + } + } + + setModified(); + emit textChanged(); + if ( !removeSelected ) { + doc->setSelectionStart( QTextDocument::Standard, &oldCursor ); + doc->setSelectionEnd( QTextDocument::Standard, cursor ); + repaintChanged(); + } + if ( hasFocus() || viewport()->hasFocus() ) { + int h = cursor->parag()->lineHeightOfChar( cursor->index() ); + if ( !readonly ) { + QFont f = cursor->parag()->at( cursor->index() )->format()->font(); + setMicroFocusHint( cursor->x() - contentsX() + frameWidth(), + cursor->y() + cursor->parag()->rect().y() - contentsY() + frameWidth(), 0, h, TRUE ); + } + } +} + +/*! Inserts \a text in the paragraph \a para and position \a index */ + +void QTextEdit::insertAt( const QString &text, int para, int index ) +{ + QTextParag *p = doc->paragAt( para ); + if ( !p ) + return; + QTextCursor tmp = *cursor; + cursor->setParag( p ); + cursor->setIndex( index ); + insert( text, FALSE, TRUE, FALSE ); + *cursor = tmp; + removeSelection( QTextDocument::Standard ); +} + +/*! Inserts \a text as the paragraph at position \a para. If \a para + is -1, the text is appended. +*/ + +void QTextEdit::insertParagraph( const QString &text, int para ) +{ + QTextParag *p = doc->paragAt( para ); + if ( p ) { + QTextCursor tmp( doc ); + tmp.setParag( p ); + tmp.setIndex( 0 ); + tmp.insert( text, TRUE ); + tmp.splitAndInsertEmptyParag(); + repaintChanged(); + } else { + append( text ); + } +} + +/*! Removes the paragraph \a para */ + +void QTextEdit::removeParagraph( int para ) +{ + QTextParag *p = doc->paragAt( para ); + if ( !p ) + return; + for ( int i = 0; i < doc->numSelections(); ++i ) + doc->removeSelection( i ); + + if ( p == doc->firstParag() && p == doc->lastParag() ) { + p->remove( 0, p->length() - 1 ); + repaintChanged(); + return; + } + drawCursor( FALSE ); + bool resetCursor = cursor->parag() == p; + if ( p->prev() ) + p->prev()->setNext( p->next() ); + else + doc->setFirstParag( p->next() ); + if ( p->next() ) + p->next()->setPrev( p->prev() ); + else + doc->setLastParag( p->prev() ); + QTextParag *start = p->next(); + int h = p->rect().height(); + delete p; + p = start; + int dy = -h; + while ( p ) { + p->setParagId( p->prev() ? p->prev()->paragId() + 1 : 0 ); + p->move( dy ); + p->invalidate( 0 ); + p->setEndState( -1 ); + p = p->next(); + } + + if ( resetCursor ) { + cursor->setParag( doc->firstParag() ); + cursor->setIndex( 0 ); + } + repaintChanged(); + drawCursor( TRUE ); +} + +/*! + Undoes the last operation. + + If there is no operation to undo, e.g. there is no undo step in the + undo/redo history, nothing happens. + + \sa undoAvailable() redo() undoDepth() +*/ + +void QTextEdit::undo() +{ + // XXX FIXME The next line is here because there may be a command + // that needs to be 'flushed'. The FIXME is because I am not + // 100% certain this is the right call to do this. + 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() ? arrowCursor : ibeamCursor ); +#endif + + clearUndoRedo(); + drawCursor( FALSE ); + QTextCursor *c = doc->undo( cursor ); + if ( !c ) { + drawCursor( TRUE ); + return; + } + lastFormatted = 0; + ensureCursorVisible(); + repaintChanged(); + drawCursor( TRUE ); + setModified(); + emit textChanged(); + if ( hasFocus() || viewport()->hasFocus() ) { + int h = cursor->parag()->lineHeightOfChar( cursor->index() ); + if ( !readonly ) { + QFont f = cursor->parag()->at( cursor->index() )->format()->font(); + setMicroFocusHint( cursor->x() - contentsX() + frameWidth(), + cursor->y() + cursor->parag()->rect().y() - contentsY() + frameWidth(), 0, h, TRUE ); + } + } +} + +/*! + Redoes the last operation. + + If there is no operation to redo, e.g. there is no redo step in the + undo/redo history, nothing happens. + + \sa redoAvailable() undo() undoDepth() +*/ + +void QTextEdit::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() ? arrowCursor : ibeamCursor ); +#endif + + clearUndoRedo(); + drawCursor( FALSE ); + QTextCursor *c = doc->redo( cursor ); + if ( !c ) { + drawCursor( TRUE ); + return; + } + lastFormatted = 0; + ensureCursorVisible(); + repaintChanged(); + ensureCursorVisible(); + drawCursor( TRUE ); + setModified(); + emit textChanged(); + if ( hasFocus() || viewport()->hasFocus() ) { + int h = cursor->parag()->lineHeightOfChar( cursor->index() ); + if ( !readonly ) { + QFont f = cursor->parag()->at( cursor->index() )->format()->font(); + setMicroFocusHint( cursor->x() - contentsX() + frameWidth(), + cursor->y() + cursor->parag()->rect().y() - contentsY() + frameWidth(), 0, h, TRUE ); + } + } +} + +/*! + 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() QTextEdit::copy() +*/ + +void QTextEdit::paste() +{ +#ifndef QT_NO_CLIPBOARD + if ( isReadOnly() ) + return; + pasteSubType( "plain" ); + if ( hasFocus() || viewport()->hasFocus() ) { + int h = cursor->parag()->lineHeightOfChar( cursor->index() ); + if ( !readonly ) { + QFont f = cursor->parag()->at( cursor->index() )->format()->font(); + setMicroFocusHint( cursor->x() - contentsX() + frameWidth(), + cursor->y() + cursor->parag()->rect().y() - contentsY() + frameWidth(), 0, h, TRUE ); + } + } +#endif +} + +void QTextEdit::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 QTextEdit::repaintChanged() +{ + if ( !isUpdatesEnabled() || !viewport()->isUpdatesEnabled() ) + return; + QPainter p( viewport() ); + p.translate( -contentsX(), -contentsY() ); + paintDocument( FALSE, &p, contentsX(), contentsY(), visibleWidth(), visibleHeight() ); +} + +/*! + 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 QTextEdit::copy() paste() pasteSubType() +*/ + +void QTextEdit::cut() +{ + if ( isReadOnly() ) + return; + + if ( doc->hasSelection( QTextDocument::Standard ) ) { + doc->copySelectedText( QTextDocument::Standard ); + removeSelectedText(); + } + if ( hasFocus() || viewport()->hasFocus() ) { + int h = cursor->parag()->lineHeightOfChar( cursor->index() ); + if ( !readonly ) { + QFont f = cursor->parag()->at( cursor->index() )->format()->font(); + setMicroFocusHint( cursor->x() - contentsX() + frameWidth(), + cursor->y() + cursor->parag()->rect().y() - contentsY() + frameWidth(), 0, h, TRUE ); + } + } +} + +/*! Copies any selected text (from selection 0) to the clipboard. + + \sa hasSelectedText() copyAvailable() + */ + +void QTextEdit::copy() +{ + if ( !doc->selectedText( QTextDocument::Standard ).isEmpty() ) + doc->copySelectedText( QTextDocument::Standard ); +} + +/*! + Re-indents the current paragraph. +*/ + +void QTextEdit::indent() +{ + if ( isReadOnly() ) + return; + + drawCursor( FALSE ); + if ( !doc->hasSelection( QTextDocument::Standard ) ) + cursor->indent(); + else + doc->indentSelection( QTextDocument::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 QTextEdit::focusNextPrevChild( bool n ) +{ + if ( !isReadOnly() || !linksEnabled() ) + return FALSE; + bool b = doc->focusNextPrevChild( n ); + repaintChanged(); + if ( b ) + //##### this does not work with tables. The focusIndicator + //should really be a QTextCursor. Fix 3.1 + makeParagVisible( doc->focusIndicator.parag ); + 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 QTextEdit::setFormat( QTextFormat *f, int flags ) +{ + if ( doc->hasSelection( QTextDocument::Standard ) ) { + drawCursor( FALSE ); + QString str = doc->selectedText( QTextDocument::Standard ); + QTextCursor c1 = doc->selectionStartCursor( QTextDocument::Standard ); + QTextCursor c2 = doc->selectionEndCursor( QTextDocument::Standard ); + clearUndoRedo(); + undoRedoInfo.type = UndoRedoInfo::Format; + undoRedoInfo.id = c1.parag()->paragId(); + undoRedoInfo.index = c1.index(); + undoRedoInfo.eid = c2.parag()->paragId(); + undoRedoInfo.eindex = c2.index(); + undoRedoInfo.d->text = str; + readFormats( c1, c2, 0, undoRedoInfo.d->text ); + undoRedoInfo.format = f; + undoRedoInfo.flags = flags; + clearUndoRedo(); + doc->setFormat( QTextDocument::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->parag()->length() - 1 ) { + currentFormat->addRef(); + cursor->parag()->string()->setFormat( cursor->index(), currentFormat, TRUE ); + if ( cursor->parag()->length() == 1 ) { + cursor->parag()->invalidate( 0 ); + cursor->parag()->format(); + repaintChanged(); + } + } + } +} + +/*! \reimp */ + +void QTextEdit::setPalette( const QPalette &p ) +{ + QScrollView::setPalette( p ); + if ( textFormat() == PlainText ) { + QTextFormat *f = doc->formatCollection()->defaultFormat(); + f->setColor( colorGroup().text() ); + updateContents( contentsX(), contentsY(), visibleWidth(), visibleHeight() ); + } +} + +/*! + Sets the paragraph style of the current paragraph + to \a dm. If \a dm is QStyleSheetItem::DisplayListItem, the + type of the list item is set to \a listStyle. + + \sa setAlignment() +*/ + +void QTextEdit::setParagType( QStyleSheetItem::DisplayMode dm, QStyleSheetItem::ListStyle listStyle ) +{ + if ( isReadOnly() ) + return; + + drawCursor( FALSE ); + if ( !doc->hasSelection( QTextDocument::Standard ) ) { + clearUndoRedo(); + undoRedoInfo.type = UndoRedoInfo::ParagType; + QValueList< QPtrVector<QStyleSheetItem> > oldStyles; + undoRedoInfo.oldStyles.clear(); + undoRedoInfo.oldStyles << cursor->parag()->styleSheetItems(); + undoRedoInfo.oldListStyles.clear(); + undoRedoInfo.oldListStyles << cursor->parag()->listStyle(); + undoRedoInfo.list = dm == QStyleSheetItem::DisplayListItem; + undoRedoInfo.listStyle = listStyle; + undoRedoInfo.id = cursor->parag()->paragId(); + undoRedoInfo.eid = cursor->parag()->paragId(); + undoRedoInfo.d->text = " "; + undoRedoInfo.index = 1; + clearUndoRedo(); + cursor->parag()->setList( dm == QStyleSheetItem::DisplayListItem, listStyle ); + repaintChanged(); + } else { + QTextParag *start = doc->selectionStart( QTextDocument::Standard ); + QTextParag *end = doc->selectionEnd( QTextDocument::Standard ); + lastFormatted = start; + clearUndoRedo(); + undoRedoInfo.type = UndoRedoInfo::ParagType; + undoRedoInfo.id = start->paragId(); + undoRedoInfo.eid = end->paragId(); + undoRedoInfo.list = dm == QStyleSheetItem::DisplayListItem; + undoRedoInfo.listStyle = listStyle; + undoRedoInfo.oldStyles.clear(); + undoRedoInfo.oldListStyles.clear(); + while ( start ) { + undoRedoInfo.oldStyles << start->styleSheetItems(); + undoRedoInfo.oldListStyles << start->listStyle(); + start->setList( dm == QStyleSheetItem::DisplayListItem, listStyle ); + if ( start == end ) + break; + start = start->next(); + } + undoRedoInfo.d->text = " "; + undoRedoInfo.index = 1; + clearUndoRedo(); + repaintChanged(); + formatMore(); + } + drawCursor( TRUE ); + setModified(); + emit textChanged(); +} + +/*! + Sets the alignment of the current paragraph to \a a. Valid alignments + are \c Qt::AlignLeft, \c Qt::AlignRight, Qt::AlignJustify and + Qt::AlignCenter (which centers horizontally). + + \sa setParagType() +*/ + +void QTextEdit::setAlignment( int a ) +{ + if ( isReadOnly() || block_set_alignment ) + return; + + drawCursor( FALSE ); + if ( !doc->hasSelection( QTextDocument::Standard ) ) { + if ( cursor->parag()->alignment() != a ) { + clearUndoRedo(); + undoRedoInfo.type = UndoRedoInfo::Alignment; + QMemArray<int> oa( 1 ); + oa[ 0 ] = cursor->parag()->alignment(); + undoRedoInfo.oldAligns = oa; + undoRedoInfo.newAlign = a; + undoRedoInfo.id = cursor->parag()->paragId(); + undoRedoInfo.eid = cursor->parag()->paragId(); + undoRedoInfo.d->text = " "; + undoRedoInfo.index = 1; + clearUndoRedo(); + cursor->parag()->setAlignment( a ); + repaintChanged(); + } + } else { + QTextParag *start = doc->selectionStart( QTextDocument::Standard ); + QTextParag *end = doc->selectionEnd( QTextDocument::Standard ); + lastFormatted = start; + int len = end->paragId() - start->paragId() + 1; + clearUndoRedo(); + undoRedoInfo.type = UndoRedoInfo::Alignment; + undoRedoInfo.id = start->paragId(); + undoRedoInfo.eid = end->paragId(); + QMemArray<int> oa( QMAX( 0, len ) ); + int i = 0; + while ( start ) { + if ( i < (int)oa.size() ) + oa[ i ] = start->alignment(); + start->setAlignment( a ); + if ( start == end ) + break; + start = start->next(); + ++i; + } + undoRedoInfo.oldAligns = oa; + undoRedoInfo.newAlign = a; + undoRedoInfo.d->text = " "; + undoRedoInfo.index = 1; + clearUndoRedo(); + repaintChanged(); + formatMore(); + } + drawCursor( TRUE ); + if ( currentAlignment != a ) { + currentAlignment = a; + emit currentAlignmentChanged( currentAlignment ); + } + setModified(); + emit textChanged(); +} + +void QTextEdit::updateCurrentFormat() +{ + int i = cursor->index(); + if ( i > 0 ) + --i; + if ( doc->useFormatCollection() && + ( !currentFormat || currentFormat->key() != cursor->parag()->at( i )->format()->key() ) ) { + if ( currentFormat ) + currentFormat->removeRef(); + currentFormat = doc->formatCollection()->format( cursor->parag()->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->parag()->alignment() ) { + currentAlignment = cursor->parag()->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 QTextEdit::setItalic( bool b ) +{ + QTextFormat f( *currentFormat ); + f.setItalic( b ); + QTextFormat *f2 = doc->formatCollection()->format( &f ); + setFormat( f2, QTextFormat::Italic ); +} + +/*! + If \a b is TRUE sets the current format to bold; otherwise sets the + current format to non-bold. + + \sa bold() +*/ + +void QTextEdit::setBold( bool b ) +{ + QTextFormat f( *currentFormat ); + f.setBold( b ); + QTextFormat *f2 = doc->formatCollection()->format( &f ); + setFormat( f2, QTextFormat::Bold ); +} + +/*! + If \a b is TRUE sets the current format to underline; otherwise sets + the current format to non-underline. + + \sa underline() +*/ + +void QTextEdit::setUnderline( bool b ) +{ + QTextFormat f( *currentFormat ); + f.setUnderline( b ); + QTextFormat *f2 = doc->formatCollection()->format( &f ); + setFormat( f2, QTextFormat::Underline ); +} + +/*! + Sets the font family of the current format to \a fontFamily. + + \sa family() setCurrentFont() +*/ + +void QTextEdit::setFamily( const QString &fontFamily ) +{ + QTextFormat f( *currentFormat ); + f.setFamily( fontFamily ); + QTextFormat *f2 = doc->formatCollection()->format( &f ); + setFormat( f2, QTextFormat::Family ); +} + +/*! + Sets the point size of the current format to \a s. + + Note that if \a s is zero or negative, the behaviour of this + function is not defined. + + \sa pointSize() setCurrentFont() setFamily() +*/ + +void QTextEdit::setPointSize( int s ) +{ + QTextFormat f( *currentFormat ); + f.setPointSize( s ); + QTextFormat *f2 = doc->formatCollection()->format( &f ); + setFormat( f2, QTextFormat::Size ); +} + +/*! + Sets the color of the current format, i.e. of the text, to \a c. + + \sa color() setPaper() +*/ + +void QTextEdit::setColor( const QColor &c ) +{ + QTextFormat f( *currentFormat ); + f.setColor( c ); + QTextFormat *f2 = doc->formatCollection()->format( &f ); + setFormat( f2, QTextFormat::Color ); +} + +/*! + Sets the vertical alignment of the current format, i.e. of the text, to \a a. + + \sa color() setPaper() +*/ + +void QTextEdit::setVerticalAlignment( VerticalAlignment a ) +{ + QTextFormat f( *currentFormat ); + f.setVAlign( (QTextFormat::VerticalAlignment)a ); + QTextFormat *f2 = doc->formatCollection()->format( &f ); + setFormat( f2, QTextFormat::VAlign ); +} + +void QTextEdit::setFontInternal( const QFont &f_ ) +{ + QTextFormat f( *currentFormat ); + f.setFont( f_ ); + QTextFormat *f2 = doc->formatCollection()->format( &f ); + setFormat( f2, QTextFormat::Font ); +} + + +QString QTextEdit::text() const +{ + if ( isReadOnly() ) + return doc->originalText(); + return doc->text(); +} + +/*! + \overload + Returns the text of paragraph \a para. + + If textFormat() is \c RichText the text will contain HTML + formatting tags. +*/ + +QString QTextEdit::text( int para ) const +{ + 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 \c AutoText, + i.e. the text edit autodetects the format from \a text. + + The optional \a context is a path which the text edit's + QMimeSourceFactory uses to resolve the locations of files and images. + (See \l{QTextEdit::QTextEdit()}.) It is passed to the text edit's + QMimeSourceFactory when quering data. + + Note that the undo/redo history is cleared by this function. + + \sa text(), setTextFormat() +*/ + +void QTextEdit::setText( const QString &text, const QString &context ) +{ + if ( !isModified() && this->context() == context && this->text() == text ) + return; + + emit undoAvailable( FALSE ); + emit redoAvailable( FALSE ); + undoRedoInfo.clear(); + doc->commands()->clear(); + + lastFormatted = 0; + cursor->restoreState(); + doc->setText( text, context ); + + if ( wrapMode == FixedPixelWidth ) { + resizeContents( wrapWidth, 0 ); + doc->setWidth( wrapWidth ); + doc->setMinimumWidth( wrapWidth ); + } else { + doc->setMinimumWidth( -1 ); + resizeContents( 0, 0 ); + } + + cursor->setDocument( doc ); + lastFormatted = doc->firstParag(); + cursor->setParag( doc->firstParag() ); + cursor->setIndex( 0 ); + updateContents( contentsX(), contentsY(), visibleWidth(), visibleHeight() ); + + if ( isModified() ) + setModified( FALSE ); + emit textChanged(); + formatMore(); + updateCurrentFormat(); +} + +/*! + \property QTextEdit::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 \c AutoText, + i.e. the text edit autodetects the format of the text. + + For richtext, calling text() on an editable QTextEdit 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 QTextEdit::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 is found; otherwise returns FALSE. + + If \a para and \a index are both null the search begins from the + start of the text. If \a para and \a index are both not null, the + search begins from the \e *\a index character position in the \e + *\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 null, the number of the paragraph in which the first + character of the match was found is put into \e *\a para, and the + index position of that character within the paragraph is put into + \e *\a index. + + If \a expr is not found the function returns FALSE. If \a index + and \a para are not null and \a expr is not found, \e *\a index + and \e *\a para are undefined. +*/ + +bool QTextEdit::find( const QString &expr, bool cs, bool wo, bool forward, + int *para, int *index ) +{ + drawCursor( FALSE ); + doc->removeSelection( QTextDocument::Standard ); +#ifndef QT_NO_CURSOR + viewport()->setCursor( isReadOnly() ? arrowCursor : ibeamCursor ); +#endif + bool found = doc->find( expr, cs, wo, forward, para, index, cursor ); + ensureCursorVisible(); + drawCursor( TRUE ); + repaintChanged(); + return found; +} + +void QTextEdit::blinkCursor() +{ + if ( !cursorVisible ) + return; + bool cv = cursorVisible; + blinkCursorVisible = !blinkCursorVisible; + drawCursor( blinkCursorVisible ); + cursorVisible = cv; +} + +/*! + Sets the cursor to position \a index in paragraph \a para. + + \sa getCursorPosition() +*/ + +void QTextEdit::setCursorPosition( int para, int index ) +{ + QTextParag *p = doc->paragAt( para ); + if ( !p ) + return; + + if ( index > p->length() - 1 ) + index = p->length() - 1; + + drawCursor( FALSE ); + cursor->setParag( p ); + cursor->setIndex( index ); + ensureCursorVisible(); + drawCursor( TRUE ); + emit cursorPositionChanged( cursor ); + emit cursorPositionChanged( cursor->parag()->paragId(), cursor->index() ); +} + +/*! + This function sets the \e *\a para and \e *\a index parameters to the + current cursor position. \a para and \a index must be non-null int + pointers. + + \sa setCursorPosition() + */ + +void QTextEdit::getCursorPosition( int *para, int *index ) const +{ + if ( !para || !index ) + return; + *para = cursor->parag()->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. Existing selections which have a different id (selNum) + are not removed, existing selections which have the same id as \a + selNum are removed. + + 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 QTextEdit::setSelection( int paraFrom, int indexFrom, + int paraTo, int indexTo, int selNum ) +{ + if ( doc->hasSelection( selNum ) ) { + doc->removeSelection( selNum ); + repaintChanged(); + } + if ( selNum > doc->numSelections() - 1 ) + doc->addSelection( selNum ); + QTextParag *p1 = doc->paragAt( paraFrom ); + if ( !p1 ) + return; + QTextParag *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 ); + QTextCursor c = *cursor; + QTextCursor oldCursor = *cursor; + c.setParag( p1 ); + c.setIndex( indexFrom ); + cursor->setParag( p2 ); + cursor->setIndex( indexTo ); + doc->setSelectionStart( selNum, &c ); + doc->setSelectionEnd( selNum, cursor ); + repaintChanged(); + ensureCursorVisible(); + if ( selNum != QTextDocument::Standard ) + *cursor = oldCursor; + drawCursor( TRUE ); +} + +/*! + If there is a selection, \e *\a paraFrom is set to the number of the + paragraph in which the selection begins and \e *\a paraTo is set to + the number of the paragraph in which the selection ends. (They could + be the same.) \e *\a indexFrom is set to the index at which the + selection begins within \e *\a paraFrom, and \e *\a indexTo is set to + the index at which the selection ends within \e *\a paraTo. + + If there is no selection, \e *\a paraFrom, \e *\a indexFrom, \e *\a + paraTo and \e *\a indexTo are all set to -1. + + \a paraFrom, \a indexFrom, \a paraTo and \a indexTo must be non-null + int pointers. + + The \a selNum is the number of the selection (multiple selections + are supported). It defaults to 0 (the default selection). + + \sa setSelection() selectedText +*/ + +void QTextEdit::getSelection( int *paraFrom, int *indexFrom, + int *paraTo, int *indexTo, int selNum ) const +{ + if ( !paraFrom || !paraTo || !indexFrom || !indexTo ) + return; + if ( !doc->hasSelection( selNum ) ) { + *paraFrom = -1; + *indexFrom = -1; + *paraTo = -1; + *indexTo = -1; + return; + } + + doc->selectionStart( selNum, *paraFrom, *indexFrom ); + doc->selectionEnd( selNum, *paraTo, *indexTo ); +} + +/*! + \property QTextEdit::textFormat + \brief the text format: rich text, plain text or auto text + + The text format is one of the following: + \list + \i 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 RichText - rich text rendering. The available styles are + defined in the default stylesheet QStyleSheet::defaultSheet(). + \i AutoText - this is the default. The text edit autodetects + which rendering style is best, \c PlainText or \c RichText. This is + done by using the QStyleSheet::mightBeRichText() function. + \endlist +*/ + +void QTextEdit::setTextFormat( TextFormat format ) +{ + doc->setTextFormat( format ); +} + +Qt::TextFormat QTextEdit::textFormat() const +{ + return doc->textFormat(); +} + +/*! + Returns the number of paragraphs in the text; this could be 0. +*/ + +int QTextEdit::paragraphs() const +{ + return doc->lastParag()->paragId() + 1; +} + +/*! + Returns the number of lines in paragraph \a para, or -1 if there + is no paragraph with index \a para. +*/ + +int QTextEdit::linesOfParagraph( int para ) const +{ + QTextParag *p = doc->paragAt( para ); + if ( !p ) + return -1; + return p->lines(); +} + +/*! + Returns the length of the paragraph \a para (number of + characters), or -1 if there is no paragraph with index \a para +*/ + +int QTextEdit::paragraphLength( int para ) const +{ + QTextParag *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 QTextEdit::lines() const +{ + QTextParag *p = doc->firstParag(); + 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 QTextEdit::lineOfChar( int para, int index ) +{ + QTextParag *p = doc->paragAt( para ); + if ( !p ) + return -1; + + int idx, line; + QTextStringChar *c = p->lineStartOfChar( index, &idx, &line ); + if ( !c ) + return -1; + + return line; +} + +void QTextEdit::setModified( bool m ) +{ + bool oldModified = modified; + modified = m; + if ( modified && doc->oTextValid ) + doc->invalidateOriginalText(); + if ( oldModified != modified ) + emit modificationChanged( modified ); +} + +/*! \property QTextEdit::modified + \brief whether the document has been modified by the user +*/ + +bool QTextEdit::isModified() const +{ + return modified; +} + +void QTextEdit::setModified() +{ + if ( !isModified() ) + setModified( TRUE ); +} + +/*! + Returns TRUE if the current format is italic; otherwise returns FALSE. + + \sa setItalic() +*/ + +bool QTextEdit::italic() const +{ + return currentFormat->font().italic(); +} + +/*! + Returns TRUE if the current format is bold; otherwise returns FALSE. + + \sa setBold() +*/ + +bool QTextEdit::bold() const +{ + return currentFormat->font().bold(); +} + +/*! + Returns TRUE if the current format is underlined; otherwise returns + FALSE. + + \sa setUnderline() +*/ + +bool QTextEdit::underline() const +{ + return currentFormat->font().underline(); +} + +/*! + Returns the font family of the current format. + + \sa setFamily() setCurrentFont() setPointSize() +*/ + +QString QTextEdit::family() const +{ + return currentFormat->font().family(); +} + +/*! + Returns the point size of the font of the current format. + + \sa setFamily() setCurrentFont() setPointSize() + +*/ + +int QTextEdit::pointSize() const +{ + return currentFormat->font().pointSize(); +} + +/*! + Returns the color of the current format. + + \sa setColor() setPaper() +*/ + +QColor QTextEdit::color() const +{ + return currentFormat->color(); +} + +/*! + Returns the font of the current format. + + \sa setCurrentFont() setFamily() setPointSize() + +*/ + +QFont QTextEdit::font() const +{ + return currentFormat->font(); +} + +/*! + Returns the alignment of the current paragraph. + + \sa setAlignment() +*/ + +int QTextEdit::alignment() const +{ + return currentAlignment; +} + +void QTextEdit::startDrag() +{ +#ifndef QT_NO_DRAGANDDROP + mousePressed = FALSE; + inDoubleClick = FALSE; + QDragObject *drag = new QTextDrag( doc->selectedText( QTextDocument::Standard ), viewport() ); + if ( isReadOnly() ) { + drag->dragCopy(); + } else { + if ( drag->drag() && QDragObject::target() != this && QDragObject::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 QTextEdit::selectAll( bool select ) +{ + if ( !select ) + doc->removeSelection( QTextDocument::Standard ); + else + doc->selectAll( QTextDocument::Standard ); + repaintChanged(); + emit copyAvailable( doc->hasSelection( QTextDocument::Standard ) ); + emit selectionChanged(); +#ifndef QT_NO_CURSOR + viewport()->setCursor( isReadOnly() ? arrowCursor : ibeamCursor ); +#endif +} + +void QTextEdit::UndoRedoInfo::clear() +{ + if ( valid() ) { + if ( type == Insert || type == Return ) + doc->addCommand( new QTextInsertCommand( doc, id, index, d->text.rawData(), oldStyles, oldListStyles, oldAligns ) ); + else if ( type == Format ) + doc->addCommand( new QTextFormatCommand( doc, id, index, eid, eindex, d->text.rawData(), format, flags ) ); + else if ( type == Alignment ) + doc->addCommand( new QTextAlignmentCommand( doc, id, eid, newAlign, oldAligns ) ); + else if ( type == ParagType ) + doc->addCommand( new QTextParagTypeCommand( doc, id, eid, list, listStyle, oldStyles, oldListStyles ) ); + else if ( type != Invalid ) + doc->addCommand( new QTextDeleteCommand( doc, id, index, d->text.rawData(), oldStyles, oldListStyles, oldAligns ) ); + } + d->text = QString::null; + id = -1; + index = -1; + oldStyles.clear(); + oldListStyles.clear(); + oldAligns.resize( 0 ); +} + + +/*! + 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 QTextEdit::del() +{ + if ( doc->hasSelection( QTextDocument::Standard ) ) { + removeSelectedText(); + return; + } + + doKeyboardAction( ActionDelete ); +} + + +QTextEdit::UndoRedoInfo::UndoRedoInfo( QTextDocument *dc ) + : type( Invalid ), doc( dc ) +{ + d = new QUndoRedoInfoPrivate; + d->text = QString::null; + id = -1; + index = -1; +} + +QTextEdit::UndoRedoInfo::~UndoRedoInfo() +{ + delete d; +} + +bool QTextEdit::UndoRedoInfo::valid() const +{ + return d->text.length() > 0 && id >= 0 && index >= 0; +} + +/*! + \internal + + Resets the current format to the default format. +*/ + +void QTextEdit::resetFormat() +{ + setAlignment( Qt3::AlignAuto ); + setParagType( QStyleSheetItem::DisplayBlock, QStyleSheetItem::ListDisc ); + setFormat( doc->formatCollection()->defaultFormat(), QTextFormat::Format ); +} + +/*! Returns the QStyleSheet which is currently used in this text edit. + + \sa setStyleSheet() + */ + +QStyleSheet* QTextEdit::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 QTextEdit::setStyleSheet( QStyleSheet* styleSheet ) +{ + doc->setStyleSheet( styleSheet ); +} + +/*! + \property QTextEdit::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 QTextEdit::setPaper( const QBrush& pap ) +{ + doc->setPaper( new QBrush( pap ) ); + viewport()->setBackgroundColor( pap.color() ); + updateContents( contentsX(), contentsY(), visibleWidth(), visibleHeight() ); +} + +QBrush QTextEdit::paper() const +{ + if ( doc->paper() ) + return *doc->paper(); + return QBrush(); +} + +/*! + \property QTextEdit::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 QTextEdit::setLinkUnderline( bool b ) +{ + if ( b == doc->underlineLinks() ) + return; + doc->setUnderlineLinks( b ); + updateStyles(); +} + +bool QTextEdit::linkUnderline() const +{ + return doc->underlineLinks(); +} + +/*! Sets the text edit's mimesource factory to \a factory. See + QMimeSourceFactory for further details. + + \sa mimeSourceFactory() + */ + +void QTextEdit::setMimeSourceFactory( QMimeSourceFactory* factory ) +{ + doc->setMimeSourceFactory( factory ); +} + +/*! Returns the QMimeSourceFactory which is currently used by this + text edit. + + \sa setMimeSourceFactory() +*/ + +QMimeSourceFactory* QTextEdit::mimeSourceFactory() const +{ + return doc->mimeSourceFactory(); +} + +/*! + 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 QTextEdit::heightForWidth( int w ) const +{ + int oldw = doc->width(); + doc->doLayout( 0, w ); + int h = doc->height(); + doc->setWidth( oldw ); + doc->invalidate(); + ( (QTextEdit*)this )->formatMore(); + return h; +} + +/*! Appends the text \a text to the end of the text edit. + Note that the undo/redo history is cleared by this function. + */ + +void QTextEdit::append( const QString &text ) +{ + // flush and clear the undo/redo stack if necessary + if ( isReadOnly() && undoRedoInfo.valid() ) { + undoRedoInfo.clear(); + doc->commands()->clear(); + } + doc->removeSelection( QTextDocument::Standard ); + TextFormat f = doc->textFormat(); + if ( f == AutoText ) { + if ( QStyleSheet::mightBeRichText( text ) ) + f = RichText; + else + f = PlainText; + } + if ( f == PlainText ) { + QTextCursor oldc( *cursor ); + ensureFormatted( doc->lastParag() ); + bool scrollToEnd = contentsY() >= contentsHeight() - visibleHeight() - + ( horizontalScrollBar()->isVisible() ? horizontalScrollBar()->height() : 0 ); + if ( !scrollToEnd ) + blockEnsureCursorVisible = TRUE; + cursor->gotoEnd(); + if ( cursor->index() > 0 ) + cursor->splitAndInsertEmptyParag(); + QTextCursor oldCursor2 = *cursor; + cursor->insert( text, TRUE ); + if ( doc->useFormatCollection() && currentFormat != cursor->parag()->at( cursor->index() )->format() ) { + doc->setSelectionStart( QTextDocument::Temp, &oldCursor2 ); + doc->setSelectionEnd( QTextDocument::Temp, cursor ); + doc->setFormat( QTextDocument::Temp, currentFormat, QTextFormat::Format ); + doc->removeSelection( QTextDocument::Temp ); + } + formatMore(); + repaintChanged(); + ensureCursorVisible(); + drawCursor( TRUE ); + *cursor = oldc; + if ( !scrollToEnd ) + blockEnsureCursorVisible = FALSE; + } else if ( f == RichText ) { + doc->setRichTextInternal( text ); + repaintChanged(); + } + setModified(); + emit textChanged(); +} + +/*! \property QTextEdit::hasSelectedText + \brief whether some text is selected in selection 0 + */ + +bool QTextEdit::hasSelectedText() const +{ + return doc->hasSelection( QTextDocument::Standard ); +} + +/*!\property QTextEdit::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 \c PlainText regardless of the text + format. In a future version of Qt an HTML subset \e may be returned + depending on the text format. + + \sa hasSelectedText + */ + +QString QTextEdit::selectedText() const +{ + return doc->selectedText( QTextDocument::Standard ); +} + +bool QTextEdit::handleReadOnlyKeyEvent( QKeyEvent *e ) +{ + switch( e->key() ) { + case Key_Down: + setContentsPos( contentsX(), contentsY() + 10 ); + break; + case Key_Up: + setContentsPos( contentsX(), contentsY() - 10 ); + break; + case Key_Left: + setContentsPos( contentsX() - 10, contentsY() ); + break; + case Key_Right: + setContentsPos( contentsX() + 10, contentsY() ); + break; + case Key_PageUp: + setContentsPos( contentsX(), contentsY() - visibleHeight() ); + break; + case Key_PageDown: + setContentsPos( contentsX(), contentsY() + visibleHeight() ); + break; + case Key_Home: + setContentsPos( contentsX(), 0 ); + break; + case Key_End: + setContentsPos( contentsX(), contentsHeight() - visibleHeight() ); + break; + case Key_F16: // Copy key on Sun keyboards + copy(); + break; +#ifndef QT_NO_NETWORKPROTOCOL + case Key_Return: + case Key_Enter: + case Key_Space: { + if ( !doc->focusIndicator.href.isEmpty() ) { + QUrl u( doc->context(), doc->focusIndicator.href, TRUE ); + emitLinkClicked( u.toString( FALSE, FALSE ) ); +#ifndef QT_NO_CURSOR + viewport()->setCursor( isReadOnly() ? arrowCursor : ibeamCursor ); +#endif + } + } break; +#endif + default: + if ( e->state() & ControlButton ) { + switch ( e->key() ) { + case Key_C: case Key_F16: // Copy key on Sun keyboards + copy(); + break; + } + } + return FALSE; + } + return TRUE; +} + +/*! Returns the context of the edit. + The context is a path which the text edit's QMimeSourceFactory + uses to resolve the locations of files and images. + + \sa text +*/ + +QString QTextEdit::context() const +{ + return doc->context(); +} + +/*! + \property QTextEdit::documentTitle + \brief the title of the document parsed from the text. + + For \c PlainText the title will be an empty string. For \c RichText + the title will be the text between the \c{<title>} tags, if present, + otherwise an empty string. +*/ + +QString QTextEdit::documentTitle() const +{ + return doc->attributes()[ "title" ]; +} + +void QTextEdit::makeParagVisible( QTextParag *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 QTextEdit::scrollToAnchor( const QString& name ) +{ + if ( name.isEmpty() ) + return; + sync(); + QTextCursor cursor( doc ); + QTextParag* last = doc->lastParag(); + do { + QTextStringChar* c = cursor.parag()->at( cursor.index() ); + if( c->isAnchor() ) { + QString a = c->anchorName(); + if ( a == name || + (a.contains( '#' ) && QStringList::split( '#', a ).contains( name ) ) ) { + setContentsPos( contentsX(), QMIN( cursor.parag()->rect().top() + cursor.totalOffsetY(), contentsHeight() - visibleHeight() ) ); + return; + } + } + cursor.gotoNextLetter(); + } while( cursor.parag() != last || !cursor.atParagEnd() ); +} + +/*! If there is an anchor at position \a pos (in contents + coordinates), its name is returned, otherwise an empty string is + returned. +*/ + +QString QTextEdit::anchorAt( const QPoint& pos ) +{ + QTextCursor c( doc ); + placeCursor( pos, &c ); + return c.parag()->at( c.index() )->anchorHref(); +} + +void QTextEdit::documentWidthChanged( int w ) +{ + resizeContents( QMAX( visibleWidth(), w), contentsHeight() ); +} + +/*! + Updates all the rendering styles used to display the text. You will + probably want to call this function after calling setStyleSheet(). +*/ + +void QTextEdit::updateStyles() +{ + doc->updateStyles(); + updateContents( contentsX(), contentsY(), visibleWidth(), visibleHeight() ); +} + +void QTextEdit::setDocument( QTextDocument *dc ) +{ + if ( dc == doc ) + return; + doc = dc; + cursor->setDocument( doc ); + clearUndoRedo(); + 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() QTextEdit::copy() +*/ +void QTextEdit::pasteSubType( const QCString& subtype ) +{ + QCString st = subtype; + QString t = QApplication::clipboard()->text(st); + if ( !t.isEmpty() ) { +#if defined(Q_OS_WIN32) + // Need to convert CRLF to LF + int index = t.find( QString::fromLatin1("\r\n"), 0 ); + while ( index != -1 ) { + t.replace( index, 2, QChar('\n') ); + index = t.find( "\r\n", index ); + } +#elif defined(Q_OS_MAC) + //need to convert CR to LF + for( unsigned int index = 0; index < t.length(); index++ ) + if(t[index] == '\r') + t[index] = '\n'; +#endif + for ( int i=0; (uint) i<t.length(); i++ ) { + if ( t[ i ] < ' ' && t[ i ] != '\n' && t[ i ] != '\t' ) + t[ i ] = ' '; + } + if ( !t.isEmpty() ) + insert( t, FALSE, TRUE, TRUE ); + } +} + +#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 QTextEdit::pasteSpecial( const QPoint& pt ) +{ + QCString st = pickSpecial( QApplication::clipboard()->data(), TRUE, pt ); + if ( !st.isEmpty() ) + pasteSubType( st ); +} +#endif +#ifndef QT_NO_MIME +QCString QTextEdit::pickSpecial( QMimeSource* ms, bool always_ask, const QPoint& pt ) +{ + if ( ms ) { +#ifndef QT_NO_POPUPMENU + QPopupMenu popup( this, "qt_pickspecial_menu" ); + QString fmt; + int n = 0; + QDict<void> done; + for (int i = 0; !( fmt = ms->format( i ) ).isNull(); i++) { + int semi = fmt.find( ";" ); + if ( semi >= 0 ) + fmt = fmt.left( semi ); + if ( fmt.left( 5 ) == "text/" ) { + fmt = fmt.mid( 5 ); + if ( !done.find( fmt ) ) { + done.insert( fmt,(void*)1 ); + popup.insertItem( fmt, i ); + n++; + } + } + } + if ( n ) { + int i = n ==1 && !always_ask ? popup.idAt( 0 ) : popup.exec( pt ); + if ( i >= 0 ) + return popup.text(i).latin1(); + } +#else + QString fmt; + for (int i = 0; !( fmt = ms->format( i ) ).isNull(); i++) { + int semi = fmt.find( ";" ); + if ( semi >= 0 ) + fmt = fmt.left( semi ); + if ( fmt.left( 5 ) == "text/" ) { + fmt = fmt.mid( 5 ); + return fmt.latin1(); + } + } +#endif + } + return QCString(); +} +#endif // QT_NO_MIME +#endif // QT_NO_CLIPBOARD + +/*! \enum QTextEdit::WordWrap + + This enum defines the QTextEdit's word wrap modes. The following + values are valid: + + \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 QTextEdit::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 QTextEdit::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( contentsX(), contentsY(), visibleWidth(), visibleHeight() ); + lastFormatted = doc->firstParag(); + 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; + } +} + +QTextEdit::WordWrap QTextEdit::wordWrap() const +{ + return wrapMode; +} + +/*! + \property QTextEdit::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 QTextEdit::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( contentsX(), contentsY(), visibleWidth(), visibleHeight() ); + lastFormatted = doc->firstParag(); + interval = 0; + formatMore(); +} + +int QTextEdit::wrapColumnOrWidth() const +{ + if ( wrapMode == WidgetWidth ) + return visibleWidth(); + return wrapWidth; +} + + +/*! \enum QTextEdit::WrapPolicy + + This enum defines where text can be wrapped in word wrap mode. + + The following values are valid: + \value AtWhiteSpace Break lines at whitespace, e.g. spaces or + newlines. + \value Anywhere Break anywhere, including within words. + \value AtWordBoundary Don't use this deprecated value (it is a + synonym for AtWhiteSpace which you should use instead). + + \sa setWrapPolicy() +*/ + +/*! + \property QTextEdit::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 AtWhiteSpace (the default) and \c + Anywhere. + + \sa wordWrap + */ + +void QTextEdit::setWrapPolicy( WrapPolicy policy ) +{ + if ( wPolicy == policy ) + return; + wPolicy = policy; + QTextFormatter *formatter; + if ( policy == AtWhiteSpace ) + formatter = new QTextFormatterBreakWords; + else + formatter = new QTextFormatterBreakInWords; + formatter->setWrapAtColumn( document()->formatter()->wrapAtColumn() ); + formatter->setWrapEnabled( document()->formatter()->isWrapEnabled( 0 ) ); + document()->setFormatter( formatter ); + doc->invalidate(); + updateContents( contentsX(), contentsY(), visibleWidth(), visibleHeight() ); + lastFormatted = doc->firstParag(); + interval = 0; + formatMore(); +} + +QTextEdit::WrapPolicy QTextEdit::wrapPolicy() const +{ + return wPolicy; +} + +/*! + Deletes all the text in the text edit. + + \sa cut() removeSelectedText() setText() + +*/ + +void QTextEdit::clear() +{ + // make clear undoable + doc->selectAll( QTextDocument::Temp ); + removeSelectedText( QTextDocument::Temp ); + + setContentsPos( 0, 0 ); + if ( cursor->isValid() ) + cursor->restoreState(); + doc->clear( TRUE ); + cursor->setDocument( doc ); + cursor->setParag( doc->firstParag() ); + cursor->setIndex( 0 ); + lastFormatted = 0; + updateContents( contentsX(), contentsY(), visibleWidth(), visibleHeight() ); + + emit cursorPositionChanged( cursor ); + emit cursorPositionChanged( cursor->parag()->paragId(), cursor->index() ); +} + +int QTextEdit::undoDepth() const +{ + return document()->undoDepth(); +} + +/*! + \property QTextEdit::length + \brief the number of characters in the text + +*/ + +int QTextEdit::length() const +{ + return document()->length(); +} + +/*! + \property QTextEdit::tabStopWidth + \brief the tab stop width in pixels + +*/ + +int QTextEdit::tabStopWidth() const +{ + return document()->tabStopWidth(); +} + +void QTextEdit::setUndoDepth( int d ) +{ + document()->setUndoDepth( d ); +} + +void QTextEdit::setTabStopWidth( int ts ) +{ + document()->setTabStops( ts ); + doc->invalidate(); + lastFormatted = doc->firstParag(); + interval = 0; + formatMore(); + updateContents( contentsX(), contentsY(), visibleWidth(), visibleHeight() ); +} + +/*! \reimp */ + +QSize QTextEdit::sizeHint() const +{ + // ### calculate a reasonable one + return QSize( 100, 100 ); +} + +void QTextEdit::clearUndoRedo() +{ + undoRedoInfo.clear(); + emit undoAvailable( doc->commands()->isUndoAvailable() ); + emit redoAvailable( doc->commands()->isRedoAvailable() ); +} + +/*! 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 QTextEdit::getFormat( int para, int index, QFont *font, QColor *color, VerticalAlignment *verticalAlignment ) +{ + if ( !font || !color ) + return FALSE; + QTextParag *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; +} + +/*! 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 QStyleSheetItem::DisplayListItem) and \a + listDepth to the depth of the list (if the display mode is + QStyleSheetItem::DisplayListItem). + + Returns FALSE if \a para is out of range otherwise returns TRUE. +*/ + +bool QTextEdit::getParagraphFormat( int para, QFont *font, QColor *color, + VerticalAlignment *verticalAlignment, int *alignment, + QStyleSheetItem::DisplayMode *displayMode, + QStyleSheetItem::ListStyle *listStyle, + int *listDepth ) +{ + if ( !font || !color || !alignment || !displayMode || !listStyle ) + return FALSE; + QTextParag *p = doc->paragAt( para ); + if ( !p ) + return FALSE; + *font = p->paragFormat()->font(); + *color = p->paragFormat()->color(); + *verticalAlignment = (VerticalAlignment)p->paragFormat()->vAlign(); + *alignment = p->alignment(); + *displayMode = p->style() ? p->style()->displayMode() : QStyleSheetItem::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. +*/ + +QPopupMenu *QTextEdit::createPopupMenu( const QPoint& pos ) +{ +#ifndef QT_NO_POPUPMENU + QPopupMenu *popup = new QPopupMenu( 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->insertSeparator(); + } +#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->insertSeparator(); + } +#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( QTextDocument::Standard, TRUE ) ); + popup->setItemEnabled( d->id[ IdCopy ], doc->hasSelection( QTextDocument::Standard, TRUE ) ); + popup->setItemEnabled( d->id[ IdPaste ], !isReadOnly() && !QApplication::clipboard()->text().isEmpty() ); +#endif + popup->setItemEnabled( d->id[ IdClear ], !isReadOnly() && !text().isEmpty() ); + popup->setItemEnabled( d->id[ IdSelectAll ], (bool)text().length() ); + 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. +*/ + +QPopupMenu *QTextEdit::createPopupMenu() +{ + return 0; +} + +/*! \reimp */ + +void QTextEdit::setFont( const QFont &f ) +{ + QFont old( QScrollView::font() ); + QScrollView::setFont( f ); + doc->setMinimumWidth( -1 ); + + // ### that is a bit hacky + static short diff = 1; + diff *= -1; + doc->setWidth( visibleWidth() + diff ); + + int s = f.pointSize(); + bool usePixels = FALSE; + if ( s == -1 ) { + s = f.pixelSize(); + usePixels = TRUE; + } + doc->updateFontSizes( s, usePixels ); + doc->updateFontAttributes( f, old ); + lastFormatted = doc->firstParag(); + formatMore(); + repaintChanged(); +} + +/*! \fn QTextEdit::zoomIn() + + \overload + + Zooms in on the text by by making the base font size one + point larger and recalculating all font sizes. This does not change + the size of any images. + + \sa zoomOut() + +*/ + +/*! \fn QTextEdit::zoomOut() + + \overload + + Zooms out on the text by by making the base font size one + point smaller and recalculating all font sizes. This does not change + the size of any images. + + \sa zoomIn() +*/ + + +/*! + Zooms in on the text by by making the base font size \a range + points larger and recalculating all font sizes. This does not change + the size of any images. + + \sa zoomOut() +*/ + +void QTextEdit::zoomIn( int range ) +{ + QFont f( QScrollView::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. This does not + change the size of any images. + + \sa zoomIn() +*/ + +void QTextEdit::zoomOut( int range ) +{ + QFont f( QScrollView::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. This does not change the size of any + images. +*/ + +void QTextEdit::zoomTo( int size ) +{ + QFont f( QScrollView::font() ); + f.setPointSize( size ); + setFont( f ); +} + +/*! + \internal + + QTextEdit 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 QTextEdit::sync() +{ + QTextParag *p = lastFormatted; + while ( p ) { + p->format(); + p = p->next(); + } + resizeContents( contentsWidth(), doc->height() ); +} + +/*! \reimp */ + +void QTextEdit::setEnabled( bool b ) +{ + QScrollView::setEnabled( b ); + if ( !b ) { + blinkTimer->stop(); + drawCursor( FALSE ); + } + if ( textFormat() == PlainText ) { + QTextFormat *f = doc->formatCollection()->defaultFormat(); + f->setColor( colorGroup().text() ); + updateContents( contentsX(), contentsY(), visibleWidth(), visibleHeight() ); + } + if ( b ) { + blinkTimer->start( QApplication::cursorFlashTime() / 2 ); + drawCursor( TRUE ); + } +} + +/*! + 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 colorGroup() of this widget. +*/ + +void QTextEdit::setSelectionAttributes( int selNum, const QColor &back, bool invertText ) +{ + if ( selNum < 1 ) + return; + if ( selNum > doc->numSelections() ) + doc->addSelection( selNum ); + doc->setSelectionColor( selNum, back ); + doc->setInvertSelectionText( selNum, invertText ); +} + +/*! \reimp */ +void QTextEdit::windowActivationChange( bool ) +{ + if ( !isVisible() ) + return; + + if ( palette().active() != palette().inactive() ) + updateContents( contentsX(), contentsY(), visibleWidth(), visibleHeight() ); +} + +void QTextEdit::setReadOnly( bool b ) +{ + if ( readonly == b ) + return; + readonly = b; +#ifndef QT_NO_CURSOR + if ( readonly ) + viewport()->setCursor( arrowCursor ); + else + viewport()->setCursor( ibeamCursor ); +#endif +} + +/*! Scrolls to the bottom of the document and does formatting if + required */ + +void QTextEdit::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 QTextEdit::paragraphRect( int para ) const +{ + QTextEdit *that = (QTextEdit *)this; + that->sync(); + QTextParag *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), or -1 if there is no paragraph with index \a pos. +*/ + +int QTextEdit::paragraphAt( const QPoint &pos ) const +{ + QTextCursor c( doc ); + c.place( pos, doc->firstParag() ); + if ( c.parag() ) + return c.parag()->paragId(); + return -1; +} + +/*! + Returns the index of the character (relative to its paragraph) at + position \a pos (in contents coordinates). If \a para is not null, + \e *\a para is set to this paragraph. If there is no character at + \a pos, -1 is returned. +*/ + +int QTextEdit::charAt( const QPoint &pos, int *para ) const +{ + QTextCursor c( doc ); + c.place( pos, doc->firstParag() ); + if ( c.parag() ) { + if ( para ) + *para = c.parag()->paragId(); + return c.index(); + } + return -1; +} + +/*! Sets the background color of the paragraph \a para to \a bg */ + +void QTextEdit::setParagraphBackgroundColor( int para, const QColor &bg ) +{ + QTextParag *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 QTextEdit::clearParagraphBackground( int para ) +{ + QTextParag *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 QTextEdit::paragraphBackgroundColor( int para ) const +{ + QTextParag *p = doc->paragAt( para ); + if ( !p ) + return QColor(); + QColor *c = p->backgroundColor(); + if ( c ) + return *c; + return QColor(); +} + +/*! \property QTextEdit::undoRedoEnabled + \brief whether undo/redo is enabled + + The default is TRUE. +*/ + +void QTextEdit::setUndoRedoEnabled( bool b ) +{ + undoEnabled = b; +} + +bool QTextEdit::isUndoRedoEnabled() const +{ + return undoEnabled; +} + +/*! Returns whether undo is available */ + +bool QTextEdit::isUndoAvailable() const +{ + return doc->commands()->isUndoAvailable() || undoRedoInfo.valid(); +} + +/*! Returns whether redo is available */ + +bool QTextEdit::isRedoAvailable() const +{ + return doc->commands()->isRedoAvailable(); +} + +void QTextEdit::ensureFormatted( QTextParag *p ) +{ + while ( !p->isValid() ) { + if ( !lastFormatted ) + return; + formatMore(); + } +} + +/*! \internal */ +void QTextEdit::updateCursor( const QPoint & pos ) +{ + if ( isReadOnly() && linksEnabled() ) { + QTextCursor c = *cursor; + placeCursor( pos, &c, TRUE ); + +#ifndef QT_NO_NETWORKPROTOCOL + if ( c.parag() && c.parag()->at( c.index() ) && + c.parag()->at( c.index() )->isAnchor() && + !c.parag()->at( c.index() )->anchorHref().isEmpty() ) { + if ( c.index() < c.parag()->length() - 1 ) + onLink = c.parag()->at( c.index() )->anchorHref(); + else + onLink = QString::null; + +#ifndef QT_NO_CURSOR + viewport()->setCursor( onLink.isEmpty() ? arrowCursor : pointingHandCursor ); +#endif + QUrl u( doc->context(), onLink, TRUE ); + emitHighlighted( u.toString( FALSE, FALSE ) ); + } else { +#ifndef QT_NO_CURSOR + viewport()->setCursor( isReadOnly() ? arrowCursor : ibeamCursor ); +#endif + onLink = QString::null; + emitHighlighted( QString::null ); + } +#endif + } +} + +void QTextEdit::placeCursor( const QPoint &pos, QTextCursor *c ) +{ + placeCursor( pos, c, FALSE ); +} diff --git a/noncore/apps/opie-write/qtextedit.h b/noncore/apps/opie-write/qtextedit.h new file mode 100644 index 0000000..b4e5701 --- a/dev/null +++ b/noncore/apps/opie-write/qtextedit.h @@ -0,0 +1,448 @@ +/**************************************************************************** +** $Id$ +** +** Definition of the QTextEdit class +** +** Created : 990101 +** +** Copyright (C) 1992-2000 Trolltech AS. All rights reserved. +** +** This file is part of the widgets module of the Qt GUI Toolkit. +** +** This file may be distributed under the terms of the Q Public License +** as defined by Trolltech AS of Norway and appearing in the file +** LICENSE.QPL included in the packaging of this file. +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition +** licenses may use this file in accordance with the Qt Commercial License +** Agreement provided with the Software. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for +** information about Qt Commercial License Agreements. +** See http://www.trolltech.com/qpl/ for QPL licensing information. +** See http://www.trolltech.com/gpl/ for GPL licensing information. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#ifndef QTEXTEDIT_H +#define QTEXTEDIT_H + +#ifndef QT_H +#include "qscrollview.h" +#include "qstylesheet.h" +#include "qvector.h" +#include "qvaluelist.h" +#endif // QT_H + +class QPainter; +class QKeyEvent; +class QResizeEvent; +class QMouseEvent; +class QTimer; +class QFont; +class QColor; +struct QUndoRedoInfoPrivate; +class QPopupMenu; + +namespace Qt3 { + +class QTextString; +class QTextDocument; +class QTextCursor; +class QTextCommand; +class QTextParag; +class QTextFormat; +class QTextBrowser; +class QTextEditPrivate; + +class Q_EXPORT QTextEdit : public QScrollView +{ + friend class QTextBrowser; + + Q_OBJECT + Q_ENUMS( WordWrap WrapPolicy ) + Q_PROPERTY( 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 ) + +public: + enum WordWrap { + NoWrap, + WidgetWidth, + FixedPixelWidth, + FixedColumnWidth + }; + + enum WrapPolicy { + AtWordBoundary, + Anywhere, + AtWhiteSpace = AtWordBoundary // deprecated, don't use + }; + + enum KeyboardAction { + ActionBackspace, + ActionDelete, + ActionReturn, + ActionKill + }; + + enum CursorAction { + MoveBackward, + MoveForward, + MoveWordBackward, + MoveWordForward, + MoveUp, + MoveDown, + MoveLineStart, + MoveLineEnd, + MoveHome, + MoveEnd, + MovePgUp, + MovePgDown + }; + + enum VerticalAlignment { + AlignNormal, + AlignSuperScript, + AlignSubScript + }; + + QTextEdit( const QString& text, const QString& context = QString::null, + QWidget* parent=0, const char* name=0); + QTextEdit( QWidget* parent=0, const char* name=0 ); + virtual ~QTextEdit(); + void setPalette( const QPalette & ); + + QString text() const; + QString text( int para ) const; + 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; + + QStyleSheet* styleSheet() const; + QMimeSourceFactory* mimeSourceFactory() const; + + 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 ); + + 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; + int alignment() const; + int undoDepth() const; + virtual bool getFormat( int para, int index, QFont *font, QColor *color, VerticalAlignment *verticalAlignment ); + virtual bool getParagraphFormat( int para, QFont *font, QColor *color, + VerticalAlignment *verticalAlignment, int *alignment, + QStyleSheetItem::DisplayMode *displayMode, + QStyleSheetItem::ListStyle *listStyle, + int *listDepth ); + bool isOverwriteMode() const { return overWrite; } + QColor paragraphBackgroundColor( int para ) const; + + bool isUndoRedoEnabled() const; + bool eventFilter( QObject *o, QEvent *e ); + +public slots: + void setEnabled( bool ); + virtual void setMimeSourceFactory( QMimeSourceFactory* factory ); + virtual void setStyleSheet( QStyleSheet* styleSheet ); + virtual void scrollToAnchor( const QString& name ); + virtual void setPaper( const QBrush& pap ); + virtual void setLinkUnderline( bool ); + + virtual void setWordWrap( WordWrap mode ); + virtual void setWrapColumnOrWidth( int ); + virtual void setWrapPolicy( WrapPolicy policy ); + + virtual void copy(); + virtual void append( const QString& text ); + + void setText( const QString &txt ) { setText( txt, QString::null ); } + virtual void setText( const QString &txt, const QString &context ); + virtual void setTextFormat( 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 QCString &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 setFont( const QFont &f ); + virtual void setVerticalAlignment( VerticalAlignment a ); + virtual void setAlignment( int a ); + virtual void setParagType( QStyleSheetItem::DisplayMode dm, QStyleSheetItem::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( QTextFormat *f, int flags ); + virtual void ensureCursorVisible(); + virtual void placeCursor( const QPoint &pos, QTextCursor *c = 0 ); + virtual void moveCursor( CursorAction action, bool select ); + virtual void doKeyboardAction( 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, bool indent, bool checkNewLine, bool removeSelected ); + 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 ); + +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( VerticalAlignment a ); + void cursorPositionChanged( QTextCursor *c ); + void cursorPositionChanged( int para, int pos ); + void returnPressed(); + void modificationChanged( bool m ); + +protected: + void repaintChanged(); + void updateStyles(); + void drawContents( QPainter *p, int cx, int cy, int cw, int ch ); + bool event( QEvent *e ); + 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 +#ifndef QT_NO_DRAGANDDROP + void contentsDragEnterEvent( QDragEnterEvent *e ); + void contentsDragMoveEvent( QDragMoveEvent *e ); + void contentsDragLeaveEvent( QDragLeaveEvent *e ); + void contentsDropEvent( QDropEvent *e ); +#endif + bool focusNextPrevChild( bool next ); + QTextDocument *document() const; + QTextCursor *textCursor() const; + void setDocument( QTextDocument *doc ); + virtual QPopupMenu *createPopupMenu( const QPoint& pos ); + virtual QPopupMenu *createPopupMenu(); + void drawCursor( bool visible ); + + void windowActivationChange( bool ); + +protected slots: + virtual void doChangeInterval(); + +private slots: + void formatMore(); + void doResize(); + void autoScrollTimerDone(); + void blinkCursor(); + void setModified(); + void startDrag(); + void documentWidthChanged( int w ); + +private: + struct Q_EXPORT UndoRedoInfo { + enum Type { Invalid, Insert, Delete, Backspace, Return, RemoveSelected, Format, Alignment, ParagType }; + + UndoRedoInfo( QTextDocument *dc ); + ~UndoRedoInfo(); + void clear(); + bool valid() const; + + QUndoRedoInfoPrivate *d; + int id; + int index; + int eid; + int eindex; + QTextFormat *format; + int flags; + Type type; + QTextDocument *doc; + QMemArray<int> oldAligns; + int newAlign; + bool list; + QStyleSheetItem::ListStyle listStyle; + QValueList< QPtrVector<QStyleSheetItem> > oldStyles; + QValueList<QStyleSheetItem::ListStyle> oldListStyles; + }; + +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( QTextParag *p ); +#ifndef QT_NO_MIME + QCString pickSpecial(QMimeSource* ms, bool always_ask, const QPoint&); +#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( QTextCursor &c1, QTextCursor &c2, int oldLen, QTextString &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( QTextParag *p ); + void placeCursor( const QPoint &pos, QTextCursor *c, bool link ); + +private: + QTextDocument *doc; + QTextCursor *cursor; + QTimer *formatTimer, *scrollTimer, *changeIntervalTimer, *blinkTimer, *dragStartTimer; + QTextParag *lastFormatted; + int interval; + UndoRedoInfo undoRedoInfo; + QTextFormat *currentFormat; + int currentAlignment; + QPoint oldMousePos, mousePos; + QPoint dragStartPos; + QString onLink; + WordWrap wrapMode; + WrapPolicy wPolicy; + int wrapWidth; + QString pressedLink; + QTextEditPrivate *d; + bool inDoubleClick : 1; + bool mousePressed : 1; + bool cursorVisible : 1; + bool blinkCursorVisible : 1; + bool readOnly : 1; + bool modified : 1; + bool mightStartDrag : 1; + bool inDnD : 1; + bool readonly : 1; + bool undoEnabled : 1; + bool overWrite : 1; +}; + +inline QTextDocument *QTextEdit::document() const +{ + return doc; +} + +inline QTextCursor *QTextEdit::textCursor() const +{ + return cursor; +} + +inline void QTextEdit::setCurrentFont( const QFont &f ) +{ + QTextEdit::setFontInternal( f ); +} + +} + +#endif //QTEXTVIEW_H |