KD Reports API Documentation  2.0
KDReportsTextDocumentData.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 **
3 ** This file is part of the KD Reports library.
4 **
5 ** SPDX-FileCopyrightText: 2007-2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
6 **
7 ** SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDAB-KDReports OR LicenseRef-KDAB-KDReports-US
8 **
9 ** Licensees holding valid commercial KD Reports licenses may use this file in
10 ** accordance with the KD Reports Commercial License Agreement provided with
11 ** the Software.
12 **
13 ** Contact info@kdab.com if any conditions of this licensing are not clear to you.
14 **
15 ****************************************************************************/
16 
23 
24 #include <QAbstractTextDocumentLayout>
25 #include <QDebug>
26 #include <QTextDocument>
27 #include <QTextTable>
28 #include <QUrl>
29 
31  : m_usesTabPositions(false)
32 {
33  m_document = new QTextDocument;
34  m_document->setUseDesignMetrics(true);
35 
37 #ifdef HAVE_KDCHART
38  ChartTextObject::registerChartTextObjectHandler(m_document);
39 #endif
40 }
41 
43 {
44  delete m_document;
45 }
46 
47 void KDReports::TextDocumentData::dumpTextValueCursors() const
48 {
49  qDebug() << "Text value cursors: (document size=" << m_document->characterCount() << ")";
50  QMultiMap<QString, TextValueData>::const_iterator it = m_textValueCursors.begin();
51  while (it != m_textValueCursors.end()) {
52  const TextValueData &data = *it;
53  if (data.cursor.isNull()) {
54  qDebug() << it.key() << "unresolved cursor at pos" << data.initialPosition;
55  } else {
56  qDebug() << it.key() << "QTextCursor currently at pos" << data.cursor.position() << "length" << data.valueLength;
57  }
58  ++it;
59  }
60 }
61 
63 {
64  resolveCursorPositions(mode);
65 }
66 
67 void KDReports::TextDocumentData::resolveCursorPositions(ModificationMode mode)
68 {
69  // We have to use QTextCursor in TextValueData so that it gets updated when
70  // we modify the document later on, but we can't just store the QTextCursor
71  // at insertion time; that cursor would be moved to the end of the document
72  // while the insertion keeps happening...
73  auto it = m_textValueCursors.begin();
74  for (; it != m_textValueCursors.end(); ++it) {
75  TextValueData &data = *it;
76  if (data.cursor.isNull()) {
77  // When appending, leave cursors "at end of document" unresolved.
78  // Otherwise they'll keep moving with insertions.
79  if (mode == Append && data.initialPosition >= m_document->characterCount() - 1) {
80  continue;
81  }
82  data.cursor = QTextCursor(m_document);
83  data.cursor.setPosition(data.initialPosition);
84  // qDebug() << "Cursor for" << it.key() << "resolved at position" << data.initialPosition;
85  }
86  }
87  // dumpTextValueCursors();
88 }
89 
90 void KDReports::TextDocumentData::setTextValueMarker(int pos, const QString &id, int valueLength, bool html)
91 {
92  // qDebug() << "setTextValueMarker" << pos << id << valueLength << "in doc" << m_document;
93  TextValueData val;
94  val.valueLength = valueLength;
95  val.elementType = html ? ElementTypeHtml : ElementTypeText;
96  val.initialPosition = pos;
97  m_textValueCursors.insert(id, val);
98 }
99 
100 void KDReports::TextDocumentData::updateTextValue(const QString &id, const QString &newValue)
101 {
102  aboutToModifyContents(Modify);
103 
104  // qDebug() << "updateTextValue: looking for id" << id << "in doc" << m_document;
105 
106  QMultiMap<QString, TextValueData>::iterator it = m_textValueCursors.find(id);
107  while (it != m_textValueCursors.end() && it.key() == id) {
108  TextValueData &data = *it;
109  // qDebug() << "Found at position" << data.cursor.position() << "length" << data.valueLength << "replacing with new value" << newValue;
110 
111  QTextCursor c(data.cursor);
112  const int oldPos = data.cursor.position();
113  c.setPosition(oldPos + data.valueLength, QTextCursor::KeepAnchor);
114  const bool html = data.elementType == ElementTypeHtml;
115  if (html)
116  c.insertHtml(newValue);
117  else
118  c.insertText(newValue);
119  // update data
120  data.valueLength = c.position() - oldPos;
121  data.cursor.setPosition(oldPos);
122  // qDebug() << " stored new length" << data.valueLength;
123 
124  ++it;
125  }
126 
127  // dumpTextValueCursors();
128 }
129 
131 {
132  if (!m_hasResizableImages && !m_usesTabPositions) {
133  return;
134  }
135  QTextCursor c(m_document);
136  c.beginEditBlock();
137  if (m_hasResizableImages) {
138  do {
139  c.movePosition(QTextCursor::NextCharacter);
140  QTextCharFormat format = c.charFormat();
141  if (format.hasProperty(ResizableImageProperty)) {
142  Q_ASSERT(format.isImageFormat());
143  QTextImageFormat imageFormat = format.toImageFormat();
144  updatePercentSize(imageFormat, size);
145  // qDebug() << "updatePercentSizes: setting image to " << imageFormat.width() << "," << imageFormat.height();
146  c.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
147  c.setCharFormat(imageFormat);
148  c.movePosition(QTextCursor::NextCharacter);
149  }
150  } while (!c.atEnd());
151  }
152 
153  if (m_usesTabPositions) {
154  QTextFrameFormat rootFrameFormat = m_document->rootFrame()->frameFormat();
155  const qreal rootFrameMargins = rootFrameFormat.leftMargin() + rootFrameFormat.rightMargin();
156  QTextBlock block = m_document->firstBlock();
157  do {
158  QTextBlockFormat blockFormat = block.blockFormat();
159  QList<QTextOption::Tab> tabs = blockFormat.tabPositions();
160  // qDebug() << "Looking at block" << block.blockNumber() << "tabs:" << tabs.count();
161  if (!tabs.isEmpty()) {
162  for (int i = 0; i < tabs.count(); ++i) {
163  QTextOption::Tab &tab = tabs[i];
164  if (tab.delimiter == QLatin1Char('P') /* means Page -- see rightAlignedTab*/) {
165  if (tab.type == QTextOption::RightTab) {
166  // qDebug() << "Adjusted RightTab from" << tab.position << "to" << size.width();
167  tab.position = size.width() - rootFrameMargins;
168  } else if (tab.type == QTextOption::CenterTab) {
169  tab.position = (size.width() - rootFrameMargins) / 2;
170  }
171  }
172  }
173  blockFormat.setTabPositions(tabs);
174  // qDebug() << "Adjusted tabs:" << tabs;
175  c.setPosition(block.position());
176  c.setBlockFormat(blockFormat);
177  }
178  block = block.next();
179  } while (block.isValid());
180  }
181  c.endEditBlock();
182 }
183 
185 {
186  if (w != m_document->textWidth()) {
187  // qDebug() << "setTextWidth" << w;
188  m_document->setTextWidth(w);
189  updatePercentSizes(m_document->size());
190  }
191 }
192 
194 {
195  if (size != m_document->pageSize()) {
196  // qDebug() << "setPageSize" << size;
197  m_document->setPageSize(size);
198  updatePercentSizes(size);
199  }
200 }
201 
202 void KDReports::TextDocumentData::updatePercentSize(QTextImageFormat &imageFormat, QSizeF size)
203 {
204  // "W50" means W=50%. "H60" means H=60%.
205  QString prop = imageFormat.property(ResizableImageProperty).toString();
206  const qreal imageRatio = imageFormat.height() / imageFormat.width();
207  const qreal pageWidth = size.width();
208  const qreal pageHeight = size.height();
209  const qreal pageRatio = pageWidth ? pageHeight / pageWidth : 0;
210  if (prop[0] == QLatin1Char('T')) {
211  // qDebug() << "updatePercentSize fitToPage" << imageRatio << pageRatio;
212  if (imageRatio < pageRatio) {
213  prop = QStringLiteral("W100");
214  } else {
215  prop = QStringLiteral("H100");
216  }
217  }
218  const qreal percent = prop.mid(1).toDouble();
219  switch (prop[0].toLatin1()) {
220  case 'W': {
221  const qreal newWidth = pageWidth * percent / 100.0;
222  imageFormat.setWidth(newWidth);
223  imageFormat.setHeight(newWidth * imageRatio);
224  // ### I needed to add this -2 here for 100%-width images to fit in
225  if (percent == 100.0)
226  imageFormat.setWidth(imageFormat.width() - 2);
227  } break;
228  case 'H':
229  imageFormat.setHeight(pageHeight * percent / 100.0);
230  // ### I needed to add -6 here for 100%-height images to fit in (with Qt-4.4)
231  // and it became -9 with Qt-4.5, and even QtSw doesn't know why.
232  // Task number 241890
233  if (percent == 100.0)
234  imageFormat.setHeight(imageFormat.height() - 10);
235  imageFormat.setWidth(imageRatio ? imageFormat.height() / imageRatio : 0);
236  // qDebug() << "updatePercentSize" << size << "->" << imageFormat.width() << "x" << imageFormat.height();
237  break;
238  default:
239  qWarning("Unhandled image format property type - internal error");
240  }
241 }
242 
244 {
245  m_tables.append(table);
246 }
247 
249 {
250  QTextCursor cursor(m_document);
251  qreal currentPointSize = -1.0;
252  QTextCursor lastCursor(m_document);
253  Q_FOREVER {
254  qreal cursorFontPointSize = cursor.charFormat().fontPointSize();
255  // qDebug() << cursorFontPointSize << "last=" << currentPointSize << cursor.block().text() << "position=" << cursor.position();
256  if (cursorFontPointSize != currentPointSize) {
257  if (currentPointSize != -1.0) {
258  setFontSizeHelper(lastCursor, cursor.position() - 1, currentPointSize, factor);
259  lastCursor.setPosition(cursor.position() - 1, QTextCursor::MoveAnchor);
260  }
261  currentPointSize = cursorFontPointSize;
262  }
263  if (cursor.atEnd())
264  break;
265  cursor.movePosition(QTextCursor::NextCharacter);
266  }
267  if (currentPointSize != -1.0) {
268  setFontSizeHelper(lastCursor, cursor.position(), currentPointSize, factor);
269  }
270 
271  // Also adjust the padding in the cells so that it remains proportional,
272  // and the column constraints.
273  Q_FOREACH (QTextTable *table, m_tables) {
274  QTextTableFormat format = table->format();
275  format.setCellPadding(format.cellPadding() * factor);
276 
277  QVector<QTextLength> constraints = format.columnWidthConstraints();
278  for (int i = 0; i < constraints.size(); ++i) {
279  if (constraints[i].type() == QTextLength::FixedLength) {
280  constraints[i] = QTextLength(QTextLength::FixedLength, constraints[i].rawValue() * factor);
281  }
282  }
283  format.setColumnWidthConstraints(constraints);
284 
285  table->setFormat(format);
286  }
287 }
288 
289 void KDReports::TextDocumentData::setFontSizeHelper(QTextCursor &lastCursor, int endPosition, qreal pointSize, qreal factor)
290 {
291  if (pointSize == 0) {
292  pointSize = m_document->defaultFont().pointSize();
293  }
294  pointSize *= factor;
295  QTextCharFormat newFormat;
296  newFormat.setFontPointSize(pointSize);
297  // qDebug() << "Applying" << pointSize << "from" << lastCursor.position() << "to" << endPosition;
298  lastCursor.setPosition(endPosition, QTextCursor::KeepAnchor);
299  lastCursor.mergeCharFormat(newFormat);
300 }
301 
302 //@cond PRIVATE
304 {
305 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
306  QString htmlText = m_document->toHtml("utf-8");
307 #else
308  QString htmlText = m_document->toHtml();
309 #endif
310  htmlText.remove(QLatin1String("margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; "));
311  htmlText.remove(QLatin1String("-qt-block-indent:0; "));
312  htmlText.remove(QLatin1String("text-indent:0px;"));
313  htmlText.remove(QLatin1String("style=\"\""));
314  htmlText.remove(QLatin1String("style=\" \""));
315  return htmlText;
316 }
317 //@endcond
318 
320 {
321  registerTable(table);
322  m_autoTables.insert(table, *element); // make copy of the AutoTableElement
323 }
324 
325 //@cond PRIVATE
327 {
329  for (AutoTablesMaps::iterator it = m_autoTables.begin(); it != m_autoTables.end(); ++it)
330  lst.append(&it.value());
331  return lst;
332 }
333 
335 {
336  // qDebug() << "regenerateAutoTables" << m_autoTables.count();
337  if (m_autoTables.isEmpty())
338  return;
339  aboutToModifyContents(Modify);
340  QTextCursor(m_document).beginEditBlock();
341  // preciseDump();
342  AutoTablesMaps autoTables = m_autoTables; // make copy since it will be modified below.
343  m_autoTables.clear();
344  AutoTablesMaps::const_iterator it = autoTables.constBegin();
345  for (; it != autoTables.constEnd(); ++it) {
346  QTextTable *table = it.key();
347  const KDReports::AutoTableElement &tableElement = it.value();
348  regenerateOneTable(tableElement, table);
349  }
350  // preciseDump();
351  QTextCursor(m_document).endEditBlock();
352 }
353 
354 void KDReports::TextDocumentData::regenerateAutoTableForModel(QAbstractItemModel *model)
355 {
356  aboutToModifyContents(Modify);
357  QTextCursor(m_document).beginEditBlock();
358  AutoTablesMaps::iterator it = m_autoTables.begin();
359  for (; it != m_autoTables.end(); ++it) {
360  KDReports::AutoTableElement tableElement = it.value();
361  if (tableElement.tableModel() == model) {
362  QTextTable *table = it.key();
363  m_autoTables.erase(it);
364  regenerateOneTable(tableElement, table);
365  break;
366  }
367  }
368  QTextCursor(m_document).endEditBlock();
369 }
370 //@endcond
371 
372 void KDReports::TextDocumentData::regenerateOneTable(const KDReports::AutoTableElement &tableElement, QTextTable *table)
373 {
374  QTextCursor cursor = table->firstCursorPosition();
375  cursor.beginEditBlock();
376  cursor.movePosition(QTextCursor::PreviousCharacter);
377  QTextCursor lastCurs = table->lastCursorPosition();
378  lastCurs.setPosition(lastCurs.position() + 1);
379  QTextBlockFormat blockFormat = lastCurs.blockFormat(); // preserve page breaks
380  cursor.setPosition(table->lastCursorPosition().position() + 1, QTextCursor::KeepAnchor);
381  cursor.removeSelectedText();
382  cursor.setBlockFormat(QTextBlockFormat()); // see preciseDump during TextDocument unittest
383  m_tables.removeAll(table);
384 
385  ReportBuilder builder(*this, cursor, nullptr /* hack - assumes Report is not needed */);
386  bool isSet;
387  QFont font = tableElement.defaultFont(&isSet);
388  if (isSet) {
389  builder.setDefaultFont(font);
390  }
391  tableElement.build(builder); // this calls registerTable again
392 
393  cursor.setBlockFormat(blockFormat);
394  cursor.endEditBlock();
395 }
396 
398 {
399  Q_FOREACH (const QString &name, m_resourceNames) {
400  const QVariant v = m_document->resource(QTextDocument::ImageResource, QUrl(name));
401  QPixmap pix = v.value<QPixmap>();
402  if (!pix.isNull()) {
403  pix.save(name);
404  }
405  }
406 }
407 
408 void KDReports::TextDocumentData::addResourceName(const QString &resourceName)
409 {
410  m_resourceNames.append(resourceName);
411 }
412 
414 {
415  m_hasResizableImages = true;
416 }
417 
419 {
420  m_usesTabPositions = usesTabs;
421 }
KDReports::AutoTableElement::tableModel
QAbstractItemModel * tableModel() const
Definition: KDReportsAutoTableElement.cpp:394
KDReports::TextDocumentData::registerAutoTable
void registerAutoTable(QTextTable *table, const KDReports::AutoTableElement *element)
Definition: KDReportsTextDocumentData.cpp:319
KDReports::TextDocumentData::registerTable
void registerTable(QTextTable *table)
Definition: KDReportsTextDocumentData.cpp:243
KDReportsHLineTextObject_p.h
KDReports::TextDocumentData::scaleFontsBy
void scaleFontsBy(qreal factor)
Definition: KDReportsTextDocumentData.cpp:248
KDReports::TextDocumentData::TextDocumentData
TextDocumentData()
Definition: KDReportsTextDocumentData.cpp:30
KDReports::TextDocumentData::asHtml
QString asHtml() const
KDReports::TextDocumentData::setHasResizableImages
void setHasResizableImages()
Definition: KDReportsTextDocumentData.cpp:413
KDReports::TextDocumentData::aboutToModifyContents
void aboutToModifyContents(ModificationMode mode)
Definition: KDReportsTextDocumentData.cpp:62
KDReports::HLineTextObject::registerHLineObjectHandler
static void registerHLineObjectHandler(QTextDocument *doc)
Definition: KDReportsHLineTextObject.cpp:46
KDReports::TextDocumentData::setTextValueMarker
void setTextValueMarker(int pos, const QString &id, int valueLength, bool html)
Definition: KDReportsTextDocumentData.cpp:90
KDReports::TextDocumentData::autoTableElements
QList< KDReports::AutoTableElement * > autoTableElements()
KDReports::TextDocumentData::updateTextValue
void updateTextValue(const QString &id, const QString &newValue)
Definition: KDReportsTextDocumentData.cpp:100
KDReports::ResizableImageProperty
static const int ResizableImageProperty
Definition: KDReportsLayoutHelper_p.h:58
QList< QTextOption::Tab >
KDReports::TextDocumentData::updatePercentSizes
void updatePercentSizes(QSizeF size)
Definition: KDReportsTextDocumentData.cpp:130
KDReports::TextDocumentData::setPageSize
void setPageSize(QSizeF size)
Definition: KDReportsTextDocumentData.cpp:193
KDReports::TextDocumentData::regenerateAutoTables
void regenerateAutoTables()
KDReports::AutoTableElement::build
void build(ReportBuilder &) const override
Definition: KDReportsAutoTableElement.cpp:276
KDReports::AutoTableElement
Definition: KDReportsAutoTableElement.h:40
KDReports::TextDocumentData::ModificationMode
ModificationMode
Definition: KDReportsTextDocumentData_p.h:84
KDReportsChartTextObject_p.h
KDReports::TextDocumentData::regenerateAutoTableForModel
void regenerateAutoTableForModel(QAbstractItemModel *model)
KDReportsReportBuilder_p.h
KDReports::TextDocumentData::updatePercentSize
static void updatePercentSize(QTextImageFormat &format, QSizeF size)
Definition: KDReportsTextDocumentData.cpp:202
KDReports::TextDocumentData::~TextDocumentData
~TextDocumentData()
Definition: KDReportsTextDocumentData.cpp:42
KDReports::TextDocumentData::layoutWithTextWidth
void layoutWithTextWidth(qreal w)
Definition: KDReportsTextDocumentData.cpp:184
KDReports::TextDocumentData::saveResourcesToFiles
void saveResourcesToFiles()
Definition: KDReportsTextDocumentData.cpp:397
KDReports::TextDocumentData::setUsesTabPositions
void setUsesTabPositions(bool usesTabs)
Definition: KDReportsTextDocumentData.cpp:418
KDReportsAutoTableElement.h
KDReportsLayoutHelper_p.h
KDReports::AbstractTableElement::defaultFont
QFont defaultFont(bool *isSet) const
Definition: KDReportsAbstractTableElement.cpp:128
KDReports::TextDocumentData::addResourceName
void addResourceName(const QString &resourceName)
Definition: KDReportsTextDocumentData.cpp:408
KDReportsTextDocumentData_p.h

© 2007-2021 Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
https://www.kdab.com/development-resources/qt-tools/kd-reports/
Generated on Sat Jan 8 2022 02:38:32 for KD Reports API Documentation by doxygen 1.8.17