• Skip to content
  • Skip to link menu
KDE 4.5 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • Sitemap
  • Contact Us
 

kpimtextedit/richtextbuilders

kmarkupdirector.cpp

00001 /*
00002     This file is part of KDE.
00003 
00004     Copyright (c) 2008 Stephen Kelly <steveire@gmail.com>
00005 
00006     This library is free software; you can redistribute it and/or modify it
00007     under the terms of the GNU Library General Public License as published by
00008     the Free Software Foundation; either version 2 of the License, or (at your
00009     option) any later version.
00010 
00011     This library is distributed in the hope that it will be useful, but WITHOUT
00012     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
00013     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
00014     License for more details.
00015 
00016     You should have received a copy of the GNU Library General Public License
00017     along with this library; see the file COPYING.LIB.  If not, write to the
00018     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00019     02110-1301, USA.
00020 */
00021 
00022 
00023 #include "kmarkupdirector.h"
00024 #include "kmarkupdirector_p.h"
00025 
00026 #include <kdebug.h>
00027 
00028 #include <QFlags>
00029 #include <QTextDocument>
00030 #include <QTextDocumentFragment>
00031 #include <QString>
00032 #include <QStack>
00033 #include <QTextFrame>
00034 #include <QTextTable>
00035 #include <QTextList>
00036 #include <QTextCursor>
00037 #include <QTextCharFormat>
00038 #include <QMap>
00039 #include <QColor>
00040 #include <QBrush>
00041 
00042 #include "kabstractmarkupbuilder.h"
00043 
00044 KMarkupDirector::KMarkupDirector(KAbstractMarkupBuilder* builder) :
00045         d(new Private(this))
00046 {
00047     d->builder = builder;
00048 }
00049 
00050 KMarkupDirector::~KMarkupDirector()
00051 {
00052     delete d;
00053 }
00054 
00055 void KMarkupDirector::processDocumentContents(QTextFrame::iterator start, QTextFrame::iterator end)
00056 {
00057     for (QTextFrame::iterator it = start; ((!it.atEnd()) && (it != end)); ++it) {
00058         QTextFrame *frame = it.currentFrame();
00059         if (frame) {
00060             QTextTable *table = dynamic_cast<QTextTable*>(frame);
00061             if (table) {
00062                 processTable(table);
00063             } else {
00064                 processFrame(frame);
00065             }
00066         } else {
00067             processBlock(it.currentBlock());
00068         }
00069     }
00070 }
00071 
00072 void KMarkupDirector::processFrame(QTextFrame* frame)
00073 {
00074     processDocumentContents(frame->begin(), frame->end());
00075 }
00076 
00077 void KMarkupDirector::processBlock(const QTextBlock &block)
00078 {
00079     if (block.isValid()) {
00080         QTextList *list = block.textList();
00081         if (list) {
00082             // An entire list is processed when first found.
00083             // Just skip over if not the first item in a list.
00084             if ((list->item(0) == block) && (!block.previous().textList())) {
00085                 processList(block);
00086             }
00087         } else {
00088             processBlockContents(block);
00089         }
00090     }
00091 }
00092 
00093 void KMarkupDirector::processTable(QTextTable *table)
00094 {
00095     QTextTableFormat format = table->format();
00096     QVector<QTextLength> colLengths = format.columnWidthConstraints();
00097 
00098     QTextLength tableWidth = format.width();
00099     QString sWidth;
00100 
00101     if (tableWidth.type() == QTextLength::PercentageLength) {
00102         sWidth = "%1%";
00103         sWidth = sWidth.arg(tableWidth.rawValue());
00104     } else if (tableWidth.type() == QTextLength::FixedLength) {
00105         sWidth = "%1";
00106         sWidth = sWidth.arg(tableWidth.rawValue());
00107     }
00108 
00109     d->builder->beginTable(format.cellPadding(), format.cellSpacing(), sWidth);
00110 
00111     int headerRowCount = format.headerRowCount();
00112 
00113     QList<QTextTableCell> alreadyProcessedCells;
00114 
00115     for (int row = 0; row < table->rows(); ++row) {
00116         // Put a thead element around here somewhere?
00117         // if (row < headerRowCount)
00118         // {
00119         // d->builder->beginTableHeader();
00120         // }
00121 
00122         d->builder->beginTableRow();
00123 
00124         // Header attribute should really be on cells, not determined by number of rows.
00125         //http://www.webdesignfromscratch.com/html-tables.cfm
00126 
00127 
00128         for (int column = 0; column < table->columns(); ++column) {
00129 
00130             QTextTableCell tableCell = table->cellAt(row, column);
00131 
00132             int columnSpan = tableCell.columnSpan();
00133             int rowSpan = tableCell.rowSpan();
00134             if ((rowSpan > 1) || (columnSpan > 1)) {
00135                 if (alreadyProcessedCells.contains(tableCell)) {
00136                     // Already processed this cell. Move on.
00137                     continue;
00138                 } else {
00139                     alreadyProcessedCells.append(tableCell);
00140                 }
00141             }
00142 
00143             QTextLength cellWidth = colLengths.at(column);
00144 
00145             QString sCellWidth;
00146 
00147             if (cellWidth.type() == QTextLength::PercentageLength) {
00148                 sCellWidth = "%1%";
00149                 sCellWidth = sCellWidth.arg(cellWidth.rawValue());
00150             } else if (cellWidth.type() == QTextLength::FixedLength) {
00151                 sCellWidth = "%1";
00152                 sCellWidth = sCellWidth.arg(cellWidth.rawValue());
00153             }
00154 
00155             // TODO: Use THEAD instead
00156             if (row < headerRowCount) {
00157                 d->builder->beginTableHeaderCell(sCellWidth, columnSpan, rowSpan);
00158             } else {
00159                 d->builder->beginTableCell(sCellWidth, columnSpan, rowSpan);
00160             }
00161 
00162             processTableCell(tableCell);
00163 
00164             if (row < headerRowCount) {
00165                 d->builder->endTableHeaderCell();
00166             } else {
00167                 d->builder->endTableCell();
00168             }
00169         }
00170         d->builder->endTableRow();
00171     }
00172     d->builder->endTable();
00173 }
00174 
00175 void KMarkupDirector::processTableCell(const QTextTableCell &cell)
00176 {
00177     processDocumentContents(cell.begin(), cell.end());
00178 }
00179 
00180 void KMarkupDirector::processList(const QTextBlock &ablock)
00181 {
00182     QTextBlock block(ablock);
00183 
00184     QTextList *list = block.textList();
00185     if (!list) {
00186         return;
00187     }
00188 
00189     QList<QTextList*> lists;
00190 
00191     while (block.isValid() && block.textList()) {
00192         if (list->item(0) == block) {
00193             // Item zero in a list is the first block in the list of blocks that make up a list.
00194             QTextListFormat::Style style = list->format().style();
00195             d->builder->beginList(style);
00196 
00197             lists.append(list);
00198         }
00199 
00200         d->builder->beginListItem();
00201         processBlockContents(block);
00202         d->builder->endListItem();
00203 
00204         block = block.next();
00205 
00206         if (block.isValid()) {
00207             QTextList *newList = block.textList();
00208 
00209             if (!newList) {
00210                 while (!lists.isEmpty()) {
00211                     lists.removeLast();
00212                     d->builder->endList();
00213                 }
00214             } else if (newList == list) {
00215                 //Next block is on the same list; Handled on next iteration.
00216                 continue;
00217             } else if (newList != list) {
00218                 if (newList->item(0) == block) {
00219                     list = newList;
00220                     continue;
00221                 } else {
00222                     while (!lists.isEmpty()) {
00223                         if (block.textList() != lists.last()) {
00224                             lists.removeLast();
00225                             d->builder->endList();
00226                         } else {
00227                             break;
00228                         }
00229                     }
00230                     continue;
00231                 }
00232             }
00233         } else {
00234             // Next block is not valid. Maybe at EOF. Close all open lists.
00235             // TODO: Figure out how to handle lists in adjacent table cells.
00236             while (!lists.isEmpty()) {
00237                 lists.removeLast();
00238                 d->builder->endList();
00239             }
00240         }
00241     }
00242 }
00243 
00244 void KMarkupDirector::processBlockContents(const QTextBlock &block)
00245 {
00246     QTextBlockFormat blockFormat = block.blockFormat();
00247     Qt::Alignment blockAlignment = blockFormat.alignment();
00248 
00249     // TODO: decide when to use <h1> etc.
00250 
00251     if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
00252         d->builder->insertHorizontalRule();
00253         return;
00254     }
00255 
00256     QTextBlock::iterator it;
00257     it = block.begin();
00258 
00259     // The beginning is the end. This is an empty block. Insert a newline and move on.
00260     // This is what gets generated by a QTextEdit...
00261     if (it.atEnd()) {
00262 //     kDebug() << "The beginning is the end";
00263         d->builder->addNewline();
00264         return;
00265     }
00266 
00267     QTextFragment fragment = it.fragment();
00268 
00269     // .. but if a sequence such as '<br /><br />' is imported into a document with setHtml, Separator_Line
00270     // characters are inserted here within one block. See testNewlines and testNewlinesThroughQTextEdit.
00271     if (fragment.isValid()) {
00272         QTextCharFormat fragmentFormat = fragment.charFormat();
00273 
00274         if (!fragmentFormat.isImageFormat() && fragment.text().at(0).category() == QChar::Separator_Line) {
00275 
00276             // Consecutive newlines in a qtextdocument are in a single fragment if inserted with setHtml.
00277             foreach(const QChar &c, fragment.text()) {
00278 //         kDebug() << c;
00279                 if (c.category() == QChar::Separator_Line) {
00280                     d->builder->addNewline();
00281                 }
00282             }
00283             return;
00284         }
00285     }
00286 
00287     // Don't have p tags inside li tags.
00288     if (!block.textList())
00289     {
00290       // Don't instruct builders to use margins. The rich text widget doesn't have an action for them yet,
00291       // So users can't edit them. See bug http://bugs.kde.org/show_bug.cgi?id=160600
00292       d->builder->beginParagraph(blockAlignment //,
00293   //                                blockFormat.topMargin(),
00294   //                                blockFormat.bottomMargin(),
00295   //                                blockFormat.leftMargin(),
00296   //                                blockFormat.rightMargin()
00297                                 );
00298     }
00299     while (!it.atEnd()) {
00300         fragment = it.fragment();
00301         if (fragment.isValid()) {
00302             QTextCharFormat fragmentFormat = fragment.charFormat();
00303 
00304             if (fragmentFormat.isImageFormat()) {
00305                 // TODO: Close any open format elements?
00306                 QTextImageFormat imageFormat = fragmentFormat.toImageFormat();
00307                 d->builder->insertImage(imageFormat.name(), imageFormat.width(), imageFormat.height());
00308                 ++it;
00309                 continue;
00310             } else {
00311                 // The order of closing and opening tags can determine whether generated html is valid or not.
00312                 // When processing a document with formatting which appears as '<b><i>Some</i> formatted<b> text',
00313                 // the correct generated output will contain '<strong><em>Some</em> formatted<strong> text'.
00314                 // However, processing text which appears as '<i><b>Some</b> formatted<i> text' might be incorrectly rendered
00315                 // as '<strong><em>Some</strong> formatted</em> text' if tags which start at the same fragment are
00316                 // opened out of order. Here, tags are not nested properly, and the html would
00317                 // not be valid or render correctly by unforgiving parsers (like QTextEdit).
00318                 // One solution is to make the order of opening tags dynamic. In the above case, the em tag would
00319                 // be opened before the strong tag '<em><strong>Some</strong> formatted</em> text'. That would
00320                 // require knowledge of which tag is going to close first. That might be possible by examining
00321                 // the 'next' QTextFragment while processing one.
00322                 //
00323                 // The other option is to do pessimistic closing of tags.
00324                 // In the above case, this means that if a fragment has two or more formats applied (bold and italic here),
00325                 // and one of them is closed, then all tags should be closed first. They will of course be reopened
00326                 // if necessary while processing the next fragment.
00327                 // The above case would be rendered as '<strong><em>Some</em></strong><em> formatted</em> text'.
00328                 //
00329                 // The first option is taken here, as the redundant opening and closing tags in the second option
00330                 // didn't appeal.
00331                 // See testDoubleStartDifferentFinish, testDoubleStartDifferentFinishReverseOrder
00332 
00333                 d->processOpeningElements(it);
00334 
00335                 // If a sequence such as '<br /><br />' is imported into a document with setHtml, LineSeparator
00336                 // characters are inserted. Here I make sure to put them back.
00337                 QStringList sl = fragment.text().split(QChar( QChar::LineSeparator ) );
00338                 QStringListIterator i(sl);
00339                 bool paraClosed = false;
00340                 while (i.hasNext())
00341                 {
00342                   d->builder->appendLiteralText(i.next());
00343                   if (i.hasNext())
00344                   {
00345                     if (i.peekNext().isEmpty())
00346                     {
00347                       if (!paraClosed)
00348                       {
00349                         d->builder->endParagraph();
00350                         paraClosed = true;
00351                       }
00352                       d->builder->addNewline();
00353                     } else if (paraClosed) {
00354                       d->builder->beginParagraph(blockAlignment);
00355                       paraClosed = false;
00356                     }
00357                   }
00358                 }
00359 
00360                 ++it;
00361                 d->processClosingElements(it);
00362             }
00363         }
00364     }
00365 
00366     // Don't have p tags inside li tags.
00367     if (!block.textList())
00368     {
00369       d->builder->endParagraph();
00370     }
00371 
00372 }
00373 
00374 void KMarkupDirector::constructContent(QTextDocument* doc)
00375 {
00376     QTextFrame *rootFrame = doc->rootFrame();
00377     processFrame(rootFrame);
00378 }

kpimtextedit/richtextbuilders

Skip menu "kpimtextedit/richtextbuilders"
  • Main Page
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Class Members
  • Related Pages

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kblog
  • kcal
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.7.1
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal