KD Reports API Documentation  2.2
KDReportsReport.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 
11 #include "KDReportsReport.h"
12 #include "KDReportsElement.h"
13 #include "KDReportsHeader.h"
15 #include "KDReportsMainTable.h"
16 #include "KDReportsReport_p.h"
19 #include "KDReportsXmlParser_p.h"
20 
21 #include <QAbstractTextDocumentLayout>
22 #include <QApplication>
23 #include <QDebug>
24 #include <QDomDocument>
25 #include <QDomElement>
26 #include <QFile>
27 #include <QMap>
28 #include <QPainter>
29 #include <QPointer>
30 #include <QPrintDialog>
31 #include <QProgressDialog>
32 #include <QStyle>
33 #include <QStyleOption>
34 #include <QThread>
35 
36 #include <memory>
37 
38 QT_BEGIN_NAMESPACE
39 Q_GUI_EXPORT extern int qt_defaultDpi(); // This is what QTextDocument uses...
40 QT_END_NAMESPACE
41 
43  : m_layoutWidth(0)
44  , m_endlessPrinterWidth(0)
45  , m_orientation(QPageLayout::Portrait)
46  , m_pageSize(QPageSize::A4)
47  , m_marginTop(20.0)
48  , // warning, defaults are duplicated in KDReportsXmlParser.cpp
49  m_marginLeft(20.0)
50  , m_marginBottom(20.0)
51  , m_marginRight(20.0)
52  , m_headerBodySpacing(0)
53  , m_footerBodySpacing(0)
54  , m_headers()
55  , m_footers()
56  , m_watermarkRotation(0)
57  , m_watermarkColor(QColor(204, 204, 204))
58  , m_watermarkFont(QFont(QStringLiteral("Helvetica"), 48))
59  , m_watermarkImage()
60  ,
61 #if 0
62  m_numHorizontalPages( 1 ),
63  m_numVerticalPages( 0 ),
64  m_scaleFontsBy( 1.0 ),
65  m_autoScale( false ),
66  m_tableBreakingPageOrder( KDReports::Report::DownThenRight ),
67 #endif
68  m_firstPageNumber(1)
69  , m_pageContentSizeDirty(true)
70  , m_xmlElementHandler(nullptr)
71  , m_currentRow(-1)
72  , m_currentModel(nullptr)
73  , m_reportMode(KDReports::Report::WordProcessing)
74  , m_layout(new TextDocReportLayout(report))
75  , m_mainTable(new MainTable)
76  , q(report)
77 {
78 }
79 
81 {
82  delete m_layout;
83  delete m_mainTable;
84 }
85 
87 {
88  return m_layoutWidth > 0;
89 }
90 
92 {
93  // determine m_paperSize from m_pageSize if needed
94  if (m_paperSize.isEmpty()) {
95  const auto mmSize = m_pageSize.size(QPageSize::Millimeter);
96  m_paperSize = QSizeF {mmToPixels(mmSize.width()), mmToPixels(mmSize.height())};
97  if (m_orientation == QPageLayout::Landscape) {
98  m_paperSize.transpose();
99  }
100  }
101  // qDebug() << "m_paperSize=" << m_paperSize;
102  return m_paperSize;
103 }
104 
106 {
107  // We need to do a layout if
108  // m_pageContentSizeDirty is true, i.e. page size has changed etc.
109  if (m_pageContentSizeDirty) {
110  if (!wantEndlessPrinting()) {
111  setPaperSizeFromPrinter(paperSize());
112  } else {
113  // Get the document to fit into one page
114  Q_ASSERT(m_layoutWidth != 0);
115  qreal textDocWidth = m_layoutWidth - mmToPixels(m_marginLeft + m_marginRight);
116  m_paperSize = layoutAsOnePage(textDocWidth);
117 
118  qDebug() << "setPaperSizeFromPrinter: endless printer. m_layoutWidth=" << m_layoutWidth << "textDocWidth=" << textDocWidth << "single page has size" << m_paperSize << "pixels";
119 
120  /* cppcheck-suppress assertWithSideEffect */
121  Q_ASSERT(m_layout->numberOfPages() == 1);
122  }
123  // at this point m_pageContentSizeDirty has been set to false in all cases
124  }
125 
126  m_layout->ensureLayouted();
127 }
128 
129 // The height of the text doc, by calculation. Adjusted by caller, if negative.
131 {
132  qreal textDocHeight = paperSize().height() - mmToPixels(m_marginTop + m_marginBottom);
133  const qreal headerHeight = m_headers.height();
134  textDocHeight -= headerHeight;
135  textDocHeight -= mmToPixels(m_headerBodySpacing);
136  const qreal footerHeight = m_footers.height();
137  textDocHeight -= mmToPixels(m_footerBodySpacing);
138  textDocHeight -= footerHeight;
139  // qDebug() << "pageContent height (pixels): paper size" << m_paperSize.height() << "minus margins" << mmToPixels( m_marginTop + m_marginBottom )
140  // << "minus headerHeight" << headerHeight << "minus footerHeight" << footerHeight << "and spacings =" << textDocHeight;
141  return textDocHeight;
142 }
143 
145 {
146  const bool skip = rawMainTextDocHeight() <= 0;
147  if (skip) {
148  qDebug() << "Not enough height for headers and footers in this page size, hiding headers and footers.";
149  }
150  return skip;
151 }
152 
154 {
155  const qreal height = rawMainTextDocHeight();
156  const bool skip = height <= 0;
157  if (skip) {
158  qreal textDocHeight = paperSize().height() - mmToPixels(m_marginTop + m_marginBottom);
159  textDocHeight -= mmToPixels(m_headerBodySpacing);
160  textDocHeight -= mmToPixels(m_footerBodySpacing);
161  return textDocHeight;
162  }
163  return height;
164 }
165 
167 {
168  const int left = qRound(mmToPixels(m_marginLeft));
169  const int top = qRound(mmToPixels(m_marginTop));
170  const int headerHeightWithSpacing = qRound((skipHeadersFooters() ? 0 : m_headers.height()) + mmToPixels(m_headerBodySpacing));
171  const int textDocWidth = qRound(m_paperSize.width() - mmToPixels(m_marginLeft + m_marginRight));
172  const int textDocHeight = qRound(mainTextDocHeight());
173  return {left, top + headerHeightWithSpacing, textDocWidth, textDocHeight};
174 }
175 
176 /*
177  [top margin]
178  [header]
179  [m_headerBodySpacing]
180  [body]
181  [m_footerBodySpacing]
182  [footer]
183  [bottom margin]
184  */
186 {
187  Q_ASSERT(!wantEndlessPrinting()); // call ensureLayouted instead!
188 
189  m_paperSize = paperSize;
190  const qreal marginsInPixels = mmToPixels(m_marginLeft + m_marginRight);
191  qreal textDocWidth = m_paperSize.width() - marginsInPixels;
192 
193  m_headers.layoutWithTextWidth(textDocWidth);
194  m_footers.layoutWithTextWidth(textDocWidth);
195 
196  const qreal textDocHeight = mainTextDocHeight();
197 
198  // Font scaling
199  // Problem: how to re-implement this without a layout document?
200  // We would risk cumulating rounding problems...?
201  // ### 2nd problem: what about fonts in headers and footers? shouldn't they scale too?
202  // if ( m_scaleFontsBy != 1.0 )
203  // m_textDocument.scaleFontsBy( m_scaleFontsBy );
204 
205  m_layout->setPageContentSize(QSizeF(textDocWidth, textDocHeight));
206 
207  m_pageContentSizeDirty = false;
208 }
209 
210 KDReports::Header *KDReports::HeaderMap::headerForPage(int pageNumber /* 1-based */, int pageCount) const
211 {
212  Header *firstPageHeader = nullptr;
213  Header *lastPageHeader = nullptr;
214  Header *evenPagesHeader = nullptr;
215  Header *oddPagesHeader = nullptr;
216  for (const_iterator it = begin(); it != end(); ++it) {
217  const KDReports::HeaderLocations loc = it.key();
218  Header *const h = it.value();
219  if (loc & KDReports::FirstPage)
220  firstPageHeader = h;
221  if (loc & KDReports::LastPage)
222  lastPageHeader = h;
223  if (loc & KDReports::EvenPages)
224  evenPagesHeader = h;
225  if (loc & KDReports::OddPages)
226  oddPagesHeader = h;
227  }
228  if (pageNumber == 1 && firstPageHeader)
229  return firstPageHeader;
230  if (pageNumber == pageCount && lastPageHeader)
231  return lastPageHeader;
232  if (pageNumber & 1) // odd
233  return oddPagesHeader;
234  else // even
235  return evenPagesHeader;
236 }
237 
238 //@cond PRIVATE
239 KDReports::HeaderLocations KDReports::HeaderMap::headerLocation(Header *header) const
240 {
241  for (const_iterator it = begin(); it != end(); ++it) {
242  const KDReports::HeaderLocations loc = it.key();
243  Header *const h = it.value();
244  if (h == header) {
245  return loc;
246  }
247  }
248  KDReports::HeaderLocations loc;
249  return loc;
250 }
251 
252 void KDReports::ReportPrivate::paintPage(int pageNumber, QPainter &painter)
253 {
254  ensureLayouted();
255 
256  const int pageCount = m_layout->numberOfPages();
257  KDReports::Header *header = m_headers.headerForPage(pageNumber + 1, pageCount);
258  if (header) {
259  header->preparePaintingPage(pageNumber + m_firstPageNumber - 1);
260  }
261  KDReports::Header *footer = m_footers.headerForPage(pageNumber + 1, pageCount);
262  if (footer) {
263  footer->preparePaintingPage(pageNumber + m_firstPageNumber - 1);
264  }
265 
266  if (m_watermarkFunction) {
267  m_watermarkFunction(painter, pageNumber);
268  }
269 
270  const QRect textDocRect = mainTextDocRect();
271  const bool skipHeadersFooters = this->skipHeadersFooters();
272 
273  /*qDebug() << "paintPage: in pixels: top=" << top << " headerHeight=" << headerHeightWithSpacing
274  << " textDoc size:" << textDocRect.size()
275  << " bottom=" << bottom << " footerHeight=" << footerHeight;*/
276 
277  if (!m_watermarkText.isEmpty()) {
278  painter.save();
279  painter.translate(textDocRect.center());
280  painter.rotate(m_watermarkRotation);
281  painter.setFont(m_watermarkFont);
282  painter.setPen(m_watermarkColor);
283  const QSize textSize(painter.fontMetrics().size(Qt::TextSingleLine, m_watermarkText));
284  const QRect textRect(-textSize.width() / 2, -textSize.height() / 2, textSize.width(), textSize.height());
285  painter.drawText(textRect, Qt::AlignCenter, m_watermarkText);
286  painter.restore();
287  }
288 
289  if (!m_watermarkImage.isNull()) {
290  // We paint it without scaling it, for better quality.
291  // But this means the actual size depends on the zoom level or printer resolution...
292  //
293  // It also means the image could end up being bigger than the page, and we don't want that.
294  // So we scale down if necessary. But never up.
295  const QSize scaledSize = m_watermarkImage.size().scaled(textDocRect.size(), Qt::KeepAspectRatio);
296  const QRect imageRect = QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, scaledSize, textDocRect);
297  // qDebug() << "textDocRect=" << textDocRect << "size=" << scaledSize << "-> imageRect=" << imageRect;
298  painter.drawImage(imageRect, m_watermarkImage);
299  }
300 
301  painter.save();
302  // painter.setClipRect( textDocRect, Qt::IntersectClip ); // triggers a Qt-Windows bug when printing
303  painter.setClipRect(textDocRect);
304  painter.translate(textDocRect.topLeft());
305  m_layout->paintPageContent(pageNumber, painter);
306  painter.restore();
307 
308  QAbstractTextDocumentLayout::PaintContext ctx;
309  ctx.palette.setColor(QPalette::Text, Qt::black);
310  if (header && !skipHeadersFooters) {
311  painter.save();
312  const int top = qRound(mmToPixels(m_marginTop));
313  painter.translate(textDocRect.left(), top);
314  ctx.clip = painter.clipRegion().boundingRect();
315  header->doc().contentDocument().documentLayout()->draw(&painter, ctx);
316  painter.restore();
317  }
318  if (footer && !skipHeadersFooters) {
319  painter.save();
320  const int bottom = qRound(mmToPixels(m_marginBottom));
321  const int footerHeight = qRound(m_footers.height());
322  painter.translate(textDocRect.left(), m_paperSize.height() - bottom - footerHeight);
323  ctx.clip = painter.clipRegion().boundingRect();
324  footer->doc().contentDocument().documentLayout()->draw(&painter, ctx);
325  painter.restore();
326  }
327 }
328 //@endcond
329 
331 {
332  m_headers.layoutWithTextWidth(docWidth);
333  m_footers.layoutWithTextWidth(docWidth);
334 
335  const qreal docHeight = m_layout->layoutAsOnePage(docWidth);
336 
337  qreal pageWidth = docWidth + mmToPixels(m_marginLeft + m_marginRight);
338  qreal pageHeight = docHeight + mmToPixels(m_marginTop + m_marginBottom);
339  pageHeight += m_headers.height();
340  pageHeight += m_footers.height();
341 
342  m_pageContentSizeDirty = false;
343 
344  // qDebug() << "One-page document is" << pageWidth << "x" << pageHeight;
345  return QSizeF(pageWidth, pageHeight);
346 }
347 
349 {
350  m_pageContentSizeDirty = true;
351 }
352 
353 bool KDReports::ReportPrivate::doPrint(QPrinter *printer, QWidget *parent)
354 {
355  // caller has to ensure that we have been layouted for this printer already
356  const int pageCount = m_layout->numberOfPages();
357  std::unique_ptr<QProgressDialog> dialog;
358  if (m_progressDialogEnabled && QThread::currentThread() == qApp->thread()) {
359  dialog.reset(new QProgressDialog(QObject::tr("Printing"), QObject::tr("Cancel"), 0, pageCount, parent));
360  dialog->setWindowModality(Qt::ApplicationModal);
361  }
362  QPainter painter;
363  if (!painter.begin(printer)) {
364  qWarning() << "QPainter failed to initialize on the given printer";
365  return false;
366  }
367 
368  int fromPage = 0;
369  int toPage = pageCount;
370  if (printer->printRange() == QPrinter::PageRange) {
371  fromPage = printer->fromPage() - 1; // it starts at 1
372  toPage = printer->toPage(); // -1 because it starts at 1, and +1 because of '<'
373  if (toPage == 0)
374  toPage = pageCount;
375  }
376 
377  bool firstPage = true;
378  for (int pageIndex = fromPage; pageIndex < toPage; ++pageIndex) {
379  if (dialog) {
380  dialog->setValue(pageIndex);
381  if (dialog->wasCanceled())
382  break;
383  }
384  emit q->printingProgress(pageIndex);
385 
386  if (!firstPage)
387  printer->newPage();
388 
389  paintPage(pageIndex, painter);
390  firstPage = false;
391  }
392 
393  return true;
394 }
395 
396 #ifndef NDEBUG
398 {
399  // for calling from gdb
400 
401  QFile html(QFile::decodeName(fileName) + QStringLiteral(".html"));
402  Q_ASSERT(html.open(QIODevice::WriteOnly));
403  const QString htmlText = m_layout->toHtml();
404  html.write(htmlText.toUtf8());
405  html.close();
406 
407  bool oldLayoutDirty = true;
408  m_pageContentSizeDirty = false;
409  QPrinter printer;
410  q->setupPrinter(&printer);
411  printer.setOutputFileName(QFile::decodeName(fileName));
412  doPrint(&printer, nullptr);
413  printer.setOutputFileName(QString());
414  m_pageContentSizeDirty = oldLayoutDirty;
415 }
416 #endif
417 
418 typedef QMap<QString, QAbstractItemModel *> ModelMap;
419 Q_GLOBAL_STATIC(ModelMap, globalModelMap)
420 
421 QAbstractItemModel *KDReports::modelForKey(const QString &key)
422 {
423  return globalModelMap()->value(key, 0);
424 }
425 
427 {
428  return paperSize().width() - mmToPixels(m_marginLeft + m_marginRight);
429 }
430 
432 {
433  if (m_reportMode == KDReports::Report::WordProcessing)
434  return static_cast<TextDocReportLayout *>(m_layout)->builder();
435  return nullptr;
436 }
437 
439 
441  : QObject(parent)
442  , d(new ReportPrivate(this))
443 {
444  setPageSize(QPageSize::A4);
445 }
446 
448 {
449 }
450 
452 {
453  if (d->m_reportMode != WordProcessing) {
454  qWarning("KDReports: addInlineElement is only supported in WordProcessing mode");
455  } else {
456  d->builder()->addInlineElementPublic(element);
457  // don't add anything else here, it won't be called from the xml parser
458  }
459 }
460 
461 void KDReports::Report::addElement(const Element &element, Qt::AlignmentFlag horizontalAlignment, const QColor &backgroundColor)
462 {
463  if (d->m_reportMode != WordProcessing) {
464  qWarning("KDReports: addElement is only supported in WordProcessing mode");
465  } else {
466  d->builder()->addBlockElementPublic(element, horizontalAlignment, backgroundColor);
467  // don't add anything else here, it won't be called from the xml parser
468  }
469 }
470 
472 {
473  if (d->m_reportMode != WordProcessing) {
474  qWarning("KDReports: addVerticalSpacing is only supported in WordProcessing mode");
475  } else {
476  d->builder()->addVerticalSpacingPublic(space);
477  }
478 }
479 
480 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
481 void KDReports::Report::setPageSize(QPrinter::PageSize size)
482 {
483  setPageSize(static_cast<QPageSize::PageSizeId>(size));
484 }
485 #endif
486 
487 void KDReports::Report::setPageSize(QPageSize::PageSizeId size)
488 {
489  setPageSize(QPageSize {size});
490 }
491 
492 void KDReports::Report::setPageSize(const QPageSize &size)
493 {
494  d->m_pageSize = size;
495  d->m_paperSize = QSizeF();
496  d->m_pageContentSizeDirty = true;
497 }
498 
500 {
501  qreal factor = 1.0;
502  switch (unit) {
503  case QPrinter::DevicePixel:
504  break;
505  case QPrinter::Millimeter:
506  factor = mmToPixels(1.0);
507  break;
508  case QPrinter::Point:
509  factor = 72.0 * qt_defaultDpi();
510  break;
511  case QPrinter::Inch:
512  factor = qt_defaultDpi();
513  break;
514  default:
515  qWarning("Unsupported printer unit %d", unit);
516  }
517  d->m_paperSize = QSizeF(paperSize.width() * factor, paperSize.height() * factor);
518  d->m_pageContentSizeDirty = true;
519 }
520 
521 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
522 QPrinter::PageSize KDReports::Report::pageSize() const
523 {
524  return static_cast<QPrinter::PageSize>(d->m_pageSize.id());
525 }
526 #else
528 {
529  return d->m_pageSize;
530 }
531 #endif
532 
533 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
534 void KDReports::Report::setOrientation(QPrinter::Orientation orientation)
535 {
536  d->m_orientation = static_cast<QPageLayout::Orientation>(orientation);
537  d->m_paperSize = QSizeF();
538  d->m_pageContentSizeDirty = true;
539 }
540 
541 QPrinter::Orientation KDReports::Report::orientation() const
542 {
543  return static_cast<QPrinter::Orientation>(d->m_orientation);
544 }
545 #endif
546 
547 void KDReports::Report::setPageOrientation(QPageLayout::Orientation orientation)
548 {
549  d->m_orientation = orientation;
550  d->m_paperSize = QSizeF();
551  d->m_pageContentSizeDirty = true;
552 }
553 
554 QPageLayout::Orientation KDReports::Report::pageOrientation() const
555 {
556  return d->m_orientation;
557 }
558 
559 void KDReports::Report::setMargins(qreal top, qreal left, qreal bottom, qreal right)
560 {
561  d->m_marginTop = top;
562  d->m_marginLeft = left;
563  d->m_marginBottom = bottom;
564  d->m_marginRight = right;
565 
566  // We'll need to call setPaperSizeFromPrinter, to update % sizes in all text documents.
567  d->m_pageContentSizeDirty = true;
568 }
569 
570 void KDReports::Report::getMargins(qreal *top, qreal *left, qreal *bottom, qreal *right) const
571 {
572  *top = d->m_marginTop;
573  *left = d->m_marginLeft;
574  *bottom = d->m_marginBottom;
575  *right = d->m_marginRight;
576 }
577 
579 {
580  d->m_marginLeft = left;
581 
582  // We'll need to call setPaperSizeFromPrinter, to update % sizes in all text documents.
583  d->m_pageContentSizeDirty = true;
584 }
585 
587 {
588  return d->m_marginLeft;
589 }
590 
592 {
593  d->m_marginRight = right;
594 
595  // We'll need to call setPaperSizeFromPrinter, to update % sizes in all text documents.
596  d->m_pageContentSizeDirty = true;
597 }
598 
600 {
601  return d->m_marginRight;
602 }
603 
605 {
606  d->m_marginTop = top;
607 
608  // We'll need to call setPaperSizeFromPrinter, to update % sizes in all text documents.
609  d->m_pageContentSizeDirty = true;
610 }
611 
613 {
614  return d->m_marginTop;
615 }
616 
618 {
619  d->m_marginBottom = bottom;
620 
621  // We'll need to call setPaperSizeFromPrinter, to update % sizes in all text documents.
622  d->m_pageContentSizeDirty = true;
623 }
624 
626 {
627  return d->m_marginBottom;
628 }
629 
631 {
632  if (widthMM) {
633  d->m_endlessPrinterWidth = widthMM;
634  d->m_layoutWidth = mmToPixels(widthMM);
635  d->m_pageContentSizeDirty = true;
636  d->ensureLayouted();
637  } else {
638  d->m_layoutWidth = 0;
639  d->m_pageContentSizeDirty = true;
640  // caller will call setPageSize...
641  }
642 }
643 
644 void KDReports::Report::paintPage(int pageNumber, QPainter &painter)
645 {
646  d->paintPage(pageNumber, painter);
647 }
648 
650 {
651  d->ensureLayouted();
652  return d->m_layout->numberOfPages();
653 }
654 
656 {
657  qDebug() << asHtml();
658 }
659 
660 QString KDReports::Report::asHtml() const
661 {
662  return d->m_layout->toHtml();
663 }
664 
666 {
667  d->m_progressDialogEnabled = enable;
668 }
669 
671 {
672  QPrinter printer;
673  setupPrinter(&printer);
674  QPointer<QPrintDialog> dialog = new QPrintDialog(&printer, parent);
675  dialog->setMinMax(1, numberOfPages());
676  bool ok = false;
677  if (dialog->exec() == QDialog::Accepted) {
678  // Well, the user can modify the page size in the printer dialog too - ensure layout matches
679  d->ensureLayouted();
680  ok = d->doPrint(&printer, parent);
681  }
682  delete dialog;
683  return ok;
684 }
685 
686 bool KDReports::Report::print(QPrinter *printer, QWidget *parent)
687 {
688  // save the old page size
689  const auto savePageSize = pageSize();
690  if (d->wantEndlessPrinting()) {
691  // ensure that the printer is set up with the right size
692  d->ensureLayouted();
693  // was: printer->setPaperSize(d->m_paperSize, QPrinter::DevicePixel);
694  printer->setPageSize(QPageSize(d->m_paperSize * pixelsToPointsMultiplier(printer->resolution()), QPageSize::Point));
695 
696  } else {
697  // ensure that the layout matches the printer
698  d->setPaperSizeFromPrinter(printer->pageLayout().fullRectPixels(printer->resolution()).size());
699  }
700 
701  printer->setFullPage(true);
702 
703  // don't call ensureLayouted here, it would use the wrong printer!
704 
705  const bool ret = d->doPrint(printer, parent);
706 
707  // Reset the page size
708  setPageSize(savePageSize);
709 
710  return ret;
711 }
712 
713 bool KDReports::Report::exportToFile(const QString &fileName, QWidget *parent)
714 {
715  d->ensureLayouted();
716  QPrinter printer;
717  printer.setOutputFileName(fileName); // must be done before setupPrinter, since it affects DPI
718  setupPrinter(&printer);
719  const bool ret = d->doPrint(&printer, parent);
720  printer.setOutputFileName(QString());
721  return ret;
722 }
723 
724 bool KDReports::Report::exportToHtml(const QString &fileName)
725 {
726  const QString html = asHtml();
727  QFile file(fileName);
728  if (file.open(QIODevice::WriteOnly)) {
729  file.write(html.toUtf8());
730  d->m_layout->finishHtmlExport();
731  return true;
732  }
733  return false;
734 }
735 
736 bool KDReports::Report::exportToImage(QSize size, const QString &fileName, const char *format)
737 {
738  // Get the document to fit into one page
739 
740  const auto savePageSize = pageSize();
741  const qreal saveLayoutWidth = d->m_layoutWidth;
742  d->m_layoutWidth = d->m_layout->idealWidth() + mmToPixels(d->m_marginLeft + d->m_marginRight);
743  d->m_pageContentSizeDirty = true;
744  d->ensureLayouted();
745 
746  const qreal zoomFactor = qMin(( qreal )size.width() / d->m_paperSize.width(), ( qreal )size.height() / d->m_paperSize.height());
747  // qDebug() << "zoomFactor=" << zoomFactor;
748 
749  QImage image(size, QImage::Format_ARGB32_Premultiplied);
750  image.fill(Qt::white);
751 
752  QPainter painter;
753  if (!painter.begin(&image)) {
754  qWarning() << "QPainter failed to initialize on the given image of size" << size;
755  return false;
756  }
757  painter.setRenderHint(QPainter::Antialiasing);
758  painter.setRenderHint(QPainter::SmoothPixmapTransform);
759  painter.fillRect(QRectF(0, 0, size.width(), size.height()), QBrush(Qt::white));
760  painter.scale(zoomFactor, zoomFactor);
761  d->paintPage(0, painter);
762 
763  // restore textdoc size and header widths
764  d->m_layoutWidth = saveLayoutWidth;
765  setPageSize(savePageSize); // redo layout
766  qDebug() << "Saving pixmap" << image.size() << "into" << fileName << "with format" << format;
767  return image.save(fileName, format);
768 }
769 
771 {
772  if (!d->m_headers.contains(hl))
773  d->m_headers.insert(hl, new Header(this));
774  return *d->m_headers.value(hl);
775 }
776 
777 void KDReports::Report::setHeaderLocation(HeaderLocations hl, Header *header)
778 {
779  // Remove old entry for this header
780  HeaderLocations loc = d->m_headers.headerLocation(header);
781  d->m_headers.remove(loc);
782  d->m_headers.insert(hl, header);
783 }
784 
786 {
787  if (!d->m_footers.contains(hl))
788  d->m_footers.insert(hl, new Header(this));
789  return *d->m_footers.value(hl);
790 }
791 
792 void KDReports::Report::setFooterLocation(HeaderLocations hl, Footer *footer)
793 {
794  // Remove old entry for this header
795  HeaderLocations loc = d->m_footers.headerLocation(footer);
796  d->m_footers.remove(loc);
797  d->m_footers.insert(hl, footer);
798 }
799 
801 {
802  return KDReports::mmToPixels(mm);
803 }
804 
805 bool KDReports::Report::loadFromXML(QIODevice *iodevice, ErrorDetails *details)
806 {
807  QDomDocument doc;
808  // Read document from the QIODevice, check for errors
809 
810  // We need to be able to see the space in <text> </text>, this is why
811  // we activate the "report-whitespace-only-CharData" feature.
812  // Unfortunately this leads to lots of whitespace text nodes in between real
813  // elements in the rest of the document, watch out for that.
814  if (iodevice->isOpen())
815  iodevice->reset(); // need to do that to allow consecutive calls of loadFromXML()
816  else
817  iodevice->open(QIODevice::ReadOnly);
818 
819  QString errorMsg;
820  int errorLine = 0;
821  int errorColumn = 0;
822  bool ret = doc.setContent(iodevice, true, &errorMsg, &errorLine, &errorColumn);
823  if (!ret) {
824  if (details) {
825  details->setLine(errorLine);
826  details->setColumn(errorColumn);
827  details->setDriverMessage(errorMsg);
828  } else
829  qWarning("Malformed XML read in KDReports::Report::loadFromXML(): error message = %s, error line = %d, error column = %d", qPrintable(errorMsg), errorLine, errorColumn);
830  return false;
831  }
832  return loadFromXML(doc, details);
833 }
834 
835 bool KDReports::Report::loadFromXML(const QDomDocument &doc, ErrorDetails *details)
836 {
837  XmlParser parser(d->m_textValues, d->m_imageValues, d->m_xmlElementHandler, this, details);
838  d->m_pageContentSizeDirty = true;
839  return parser.processDocument(doc, d->builder());
840 }
841 
842 void KDReports::Report::associateModel(const QString &modelKey, QAbstractItemModel *model)
843 {
844  globalModelMap()->insert(modelKey, model);
845 }
846 
847 KDReports::TextDocument &KDReports::Report::doc() const
848 {
849  Q_ASSERT(d->m_reportMode == WordProcessing);
850  return static_cast<TextDocReportLayout *>(d->m_layout)->textDocument();
851 }
852 
854 {
855  d->builder()->contentDocumentCursor().beginEditBlock();
856 }
857 
859 {
860  d->builder()->contentDocumentCursor().endEditBlock();
861 }
862 
863 QString KDReports::Report::anchorAt(int pageNumber, QPoint pos) const
864 {
865  const QRect textDocRect = d->mainTextDocRect();
866  const QPoint textPos = pos - textDocRect.topLeft();
867  return d->m_layout->anchorAt(pageNumber, textPos);
868 }
869 
871 {
872  if (d->m_reportMode == WordProcessing) {
873  return &static_cast<TextDocReportLayout *>(d->m_layout)->textDocument().contentDocument();
874  } else {
875  return nullptr;
876  }
877 }
878 
879 void KDReports::Report::setWatermarkText(const QString &text, int rotation, const QColor &color, const QFont &font)
880 {
881  d->m_watermarkText = text;
882  d->m_watermarkRotation = rotation;
883  d->m_watermarkColor = color;
884  d->m_watermarkFont = font;
885 }
886 
888 {
889  return d->m_watermarkText;
890 }
891 
893 {
894  return d->m_watermarkRotation;
895 }
896 
898 {
899  return d->m_watermarkColor;
900 }
901 
903 {
904  return d->m_watermarkFont;
905 }
906 
907 void KDReports::Report::setWatermarkPixmap(const QPixmap &pixmap, bool autoGrayOut)
908 {
909  QPixmap pix(pixmap);
910  if (autoGrayOut) {
911  QStyleOption opt(0);
912  opt.palette = QApplication::palette();
913  pix = qApp->style()->generatedIconPixmap(QIcon::Disabled, pixmap, &opt);
914  }
915  setWatermarkImage(pix.toImage());
916 }
917 
919 {
920  return QPixmap::fromImage(d->m_watermarkImage);
921 }
922 
923 void KDReports::Report::setWatermarkImage(const QImage &image)
924 {
925  d->m_watermarkImage = image;
926 }
927 
929 {
930  return d->m_watermarkImage;
931 }
932 
934 {
935  d->m_watermarkFunction = std::move(function);
936 }
937 
939 {
940  return d->m_watermarkFunction;
941 }
942 
944 {
945  d->builder()->addPageBreakPublic();
946 }
947 
948 void KDReports::Report::associateTextValue(const QString &id, const QString &value)
949 {
950  d->m_layout->updateTextValue(id, value); // in case the document is built already
951  d->m_headers.updateTextValue(id, value);
952  d->m_footers.updateTextValue(id, value);
953  d->m_textValues.insert(id, value); // in case the document isn't built yet
954 }
955 
956 void KDReports::Report::associateImageValue(const QString &id, const QPixmap &value)
957 {
958  d->m_imageValues.insert(id, value.toImage());
959 }
960 
961 void KDReports::Report::associateImageValue(const QString &id, const QImage &value)
962 {
963  d->m_imageValues.insert(id, value);
964 }
965 
967 {
968  return d->paperSize();
969 }
970 
971 void KDReports::Report::addFragment(const QTextDocumentFragment &fragment)
972 {
973  d->builder()->insertFragmentPublic(fragment);
974 }
975 
976 void KDReports::Report::setDefaultFont(const QFont &font)
977 {
978  d->m_layout->setDefaultFont(font);
979  d->m_pageContentSizeDirty = true;
980 }
981 
983 {
984  return d->m_layout->defaultFont();
985 }
986 
988 {
989  d->m_headerBodySpacing = spacing;
990  d->m_pageContentSizeDirty = true;
991 }
992 
994 {
995  return d->m_headerBodySpacing;
996 }
997 
999 {
1000  d->m_footerBodySpacing = spacing;
1001  d->m_pageContentSizeDirty = true;
1002 }
1003 
1005 {
1006  return d->m_footerBodySpacing;
1007 }
1008 
1009 void KDReports::Report::scaleTo(int numPagesHorizontally, int numPagesVertically)
1010 {
1011  d->m_layout->scaleTo(numPagesHorizontally, numPagesVertically);
1012 }
1013 
1015 {
1016  return d->m_layout->maximumNumberOfPagesForHorizontalScaling();
1017 }
1018 
1020 {
1021  return d->m_layout->maximumNumberOfPagesForVerticalScaling();
1022 }
1023 
1025 {
1026  d->m_layout->setFixedRowHeight(mmToPixels(mm));
1027 }
1028 
1030 {
1031  d->m_layout->setUserRequestedFontScalingFactor(factor);
1032 }
1033 
1035 {
1036  return d->m_layout->userRequestedFontScalingFactor();
1037 }
1038 
1040 {
1041  return maximumNumberOfPagesForHorizontalScaling() != 1 || maximumNumberOfPagesForVerticalScaling() > 0;
1042 }
1043 
1045 {
1046  if (d->m_reportMode != SpreadSheet) {
1047  qWarning("setTableBreakingPageOrder is only supported in SpreadSheet mode");
1048  } else {
1049  mainTable()->setTableBreakingPageOrder(pageOrder);
1050  }
1051 }
1052 
1054 {
1055  if (d->m_reportMode != SpreadSheet) {
1056  qWarning("tableBreakingPageOrder is only supported in SpreadSheet mode");
1057  return DownThenRight;
1058  } else {
1059  return mainTable()->tableBreakingPageOrder();
1060  }
1061 }
1062 
1064 {
1065  if (d->m_reportMode == WordProcessing)
1066  doc().regenerateAutoTables();
1067 }
1068 
1069 void KDReports::Report::regenerateAutoTableForModel(QAbstractItemModel *model)
1070 {
1071  if (d->m_reportMode == WordProcessing)
1072  doc().regenerateAutoTableForModel(model);
1073 }
1074 
1076 {
1077  if (d->m_reportMode == WordProcessing)
1078  return doc().autoTableElements();
1079  return {};
1080 }
1081 
1083 {
1084  d->m_xmlElementHandler = handler;
1085 }
1086 
1087 void KDReports::Report::setCurrentRow(const QAbstractItemModel *model, int row)
1088 {
1089  d->m_currentModel = model;
1090  d->m_currentRow = row;
1091 }
1092 
1093 void KDReports::Report::setDocumentName(const QString &name)
1094 {
1095  d->m_documentName = name;
1096 }
1097 
1099 {
1100  return d->m_documentName;
1101 }
1102 
1104 {
1105  d->builder()->setTabPositions(tabs);
1106 }
1107 
1108 void KDReports::Report::setParagraphMargins(qreal left, qreal top, qreal right, qreal bottom)
1109 {
1110  d->builder()->setParagraphMargins(left, top, right, bottom);
1111 }
1112 
1114 {
1115  if (d->m_reportMode != reportMode) {
1116  d->m_reportMode = reportMode;
1117  delete d->m_layout;
1118  switch (reportMode) {
1119  case WordProcessing:
1120  d->m_layout = new TextDocReportLayout(this);
1121  break;
1122  case SpreadSheet:
1123  auto *sslayout = new SpreadsheetReportLayout(this);
1124  d->m_layout = sslayout;
1125  mainTable()->setLayout(sslayout);
1126  break;
1127  };
1128  }
1129 }
1130 
1132 {
1133  return d->m_reportMode;
1134 }
1135 
1137 {
1138  Q_ASSERT(d->m_reportMode == SpreadSheet);
1139  return d->m_mainTable;
1140 }
1141 
1143 {
1144  QTextOption::Tab tab;
1145  tab.position = -1;
1146  tab.type = QTextOption::RightTab;
1147  tab.delimiter = QChar::fromLatin1('P'); // a bit hackish, but this is how we tell TextDocumentData::updatePercentSize
1148  return tab;
1149 }
1150 
1152 {
1153  QTextOption::Tab tab;
1154  tab.position = -1;
1155  tab.type = QTextOption::CenterTab;
1156  tab.delimiter = QChar::fromLatin1('P'); // a bit hackish, but this is how we tell TextDocumentData::updatePercentSize
1157  return tab;
1158 }
1159 
1160 KDReports::HeaderLocations KDReports::Report::headerLocation(KDReports::Header *header) const
1161 {
1162  return d->m_headers.headerLocation(header);
1163 }
1164 
1165 KDReports::HeaderLocations KDReports::Report::footerLocation(KDReports::Footer *footer) const
1166 {
1167  return d->m_footers.headerLocation(footer);
1168 }
1169 
1171 {
1172  return d->builder()->currentPosition();
1173 }
1174 
1176 {
1177  d->m_firstPageNumber = num;
1178 }
1179 
1181 {
1182  return d->m_firstPageNumber;
1183 }
1184 
1185 void KDReports::Report::setupPrinter(QPrinter *printer)
1186 {
1187  printer->setFullPage(true);
1188  printer->setPageOrientation(d->m_orientation);
1189  // was: printer->setPaperSize(rawPaperSize(d->m_pageSize, printer), QPrinter::DevicePixel);
1190  printer->setPageSize(d->m_pageSize);
1191  printer->setDocName(d->m_documentName);
1192 }
QMap< QString, QAbstractItemModel * > ModelMap
QT_BEGIN_NAMESPACE Q_GUI_EXPORT int qt_defaultDpi()
void setDriverMessage(const QString &message)
KDReports::HeaderLocations headerLocation(Header *header) const
Header * headerForPage(int pageNumber, int pageCount) const
void paintPage(int pageNumber, QPainter &painter)
void debugLayoutToPdf(const char *fileName)
qreal rawMainTextDocHeight() const
void setPaperSizeFromPrinter(QSizeF paperSize)
bool doPrint(QPrinter *printer, QWidget *parent)
QSizeF layoutAsOnePage(qreal docWidth)
ReportBuilder * builder() const
ReportPrivate(Report *report)
void associateTextValue(const QString &id, const QString &value)
void addElement(const Element &element, Qt::AlignmentFlag horizontalAlignment=Qt::AlignLeft, const QColor &backgroundColor=QColor())
bool isTableBreakingEnabled() const
void setWatermarkPixmap(const QPixmap &pixmap, bool autoGrayOut=true)
void setWatermarkImage(const QImage &image)
QPageSize pageSize() const
qreal leftPageMargins() const
void setTopPageMargin(qreal top)
void setPageSize(QPageSize::PageSizeId size)
void setWatermarkText(const QString &text, int rotation=0, const QColor &color=QColor(204, 204, 204), const QFont &font=QFont(QStringLiteral("Helvetica"), 48))
QString watermarkText() const
Header & header(HeaderLocations hl=AllPages)
qreal headerBodySpacing() const
void setFontScalingFactor(qreal factor)
Scale the fonts in the document by a given factor.
qreal bottomPageMargins() const
QFont watermarkFont() const
qreal topPageMargins() const
bool print(QPrinter *printer, QWidget *parent=nullptr)
void setDefaultFont(const QFont &font)
static QTextOption::Tab rightAlignedTab()
void associateModel(const QString &modelKey, QAbstractItemModel *model)
void associateImageValue(const QString &id, const QPixmap &value)
Footer & footer(HeaderLocations hl=AllPages)
int currentPosition() const
QSizeF paperSize() const
bool exportToImage(QSize size, const QString &fileName, const char *format)
void getMargins(qreal *top, qreal *left, qreal *bottom, qreal *right) const
void paintPage(int pageNumber, QPainter &painter)
qreal rightPageMargins() const
int maximumNumberOfPagesForVerticalScaling() const
void setFixedRowHeight(qreal mm)
void setWatermarkFunction(WatermarkFunction function)
void setTableBreakingPageOrder(TableBreakingPageOrder pageOrder)
QPageLayout::Orientation pageOrientation() const
static QTextOption::Tab middleAlignedTab()
void setPaperSize(QSizeF paperSize, QPrinter::Unit unit)
void regenerateAutoTableForModel(QAbstractItemModel *model)
int watermarkRotation() const
QColor watermarkColor() const
void setTabPositions(const QList< QTextOption::Tab > &tabs)
static qreal mmToPixels(qreal mm)
QFont defaultFont() const
bool printWithDialog(QWidget *parent)
void setMargins(qreal top, qreal left, qreal bottom, qreal right)
void addVerticalSpacing(qreal space)
void setWidthForEndlessPrinter(qreal widthMM)
void addFragment(const QTextDocumentFragment &fragment)
int maximumNumberOfPagesForHorizontalScaling() const
void setHeaderLocation(HeaderLocations hl, Header *header)
void setXmlElementHandler(KDReports::XmlElementHandler *handler)
void setHeaderBodySpacing(qreal spacing)
void setProgressDialogEnabled(bool enable)
WatermarkFunction watermarkFunction() const
MainTable * mainTable() const
qreal fontScalingFactor() const
std::function< void(QPainter &, int)> WatermarkFunction
int numberOfPages() const
QTextDocument * mainTextDocument() const
bool exportToFile(const QString &fileName, QWidget *parent=nullptr)
void setCurrentRow(const QAbstractItemModel *model, int row)
ReportMode reportMode() const
void setParagraphMargins(qreal left, qreal top, qreal right, qreal bottom)
void setBottomPageMargin(qreal bottom)
void scaleTo(int numPagesHorizontally, int numPagesVertically)
Ensure that the report fits into a number of pages.
QImage watermarkImage() const
void setFirstPageNumber(int num)
Report(QObject *parent=nullptr)
QPixmap watermarkPixmap() const
qreal footerBodySpacing() const
KDReports::HeaderLocations footerLocation(KDReports::Footer *footer) const
bool exportToHtml(const QString &fileName)
void setLeftPageMargin(qreal left)
KDReports::HeaderLocations headerLocation(Header *header) const
void setRightPageMargin(qreal right)
void addInlineElement(const Element &element)
TableBreakingPageOrder tableBreakingPageOrder() const
void setReportMode(ReportMode reportMode)
void setFooterLocation(HeaderLocations hl, Footer *footer)
bool loadFromXML(QIODevice *iodevice, ErrorDetails *details=nullptr)
QString anchorAt(int pageNumber, QPoint pos) const
void setDocumentName(const QString &name)
void setPageOrientation(QPageLayout::Orientation orientation)
int firstPageNumber() const
void setFooterBodySpacing(qreal spacing)
QList< KDReports::AutoTableElement * > autoTableElements() const
bool processDocument(const QDomDocument &document, KDReports::ReportBuilder *builder)
KDREPORTS_EXPORT qreal mmToPixels(qreal mm)
KDREPORTS_EXPORT qreal pixelsToPointsMultiplier(double resolution)
QAbstractItemModel * modelForKey(const QString &key)
@ FirstPage
The first page of the report.
@ EvenPages
The even pages of the report: 2, 4, 6 etc.
@ LastPage
The last page of the report.
@ OddPages
The odd pages of the report: 1 (unless FirstPage has its own header), 3, 5, 7 etc.

© 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