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

© 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 by doxygen 1.9.1