Print: improve table printing by using QTableView

The current QTextDocument implementation is slow due to
HTML parsing. By using QTableView with QAbstractTableModel
we boost the performance of the table print drastically.
This patch completely replaces the old solution.

There is a hidden QTableView widget which is populated
with all data and rendered using a QPainter attached to
the printer device.

A couple of new classes are added in models.h/cpp
that handle the table print model and these are then used
in printlayout.h/cpp.

Signed-off-by: Lubomir I. Ivanov <neolit123@gmail.com>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This commit is contained in:
Lubomir I. Ivanov 2013-07-25 12:52:20 +03:00 committed by Dirk Hohndel
parent 1f976371bf
commit e5b18db802
4 changed files with 259 additions and 132 deletions

View file

@ -1438,3 +1438,109 @@ void YearlyStatisticsModel::update_yearly_stats()
item->parent = rootItem; item->parent = rootItem;
} }
} }
/*#################################################################
* #
* # Table Print Model
* #
* ################################################################
*/
TablePrintModel::TablePrintModel()
{
columns = 7;
rows = 0;
}
TablePrintModel::~TablePrintModel()
{
for (int i = 0; i < list.size(); i++)
delete list.at(i);
}
void TablePrintModel::insertRow(int index)
{
struct TablePrintItem *item = new struct TablePrintItem();
item->colorBackground = 0xffffffff;
if (index == -1) {
beginInsertRows(QModelIndex(), rows, rows);
list.append(item);
} else {
beginInsertRows(QModelIndex(), index, index);
list.insert(index, item);
}
endInsertRows();
rows++;
}
void TablePrintModel::callReset()
{
reset();
}
QVariant TablePrintModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role == Qt::BackgroundRole)
return QColor(list.at(index.row())->colorBackground);
if (role == Qt::DisplayRole)
switch (index.column()) {
case 0:
return list.at(index.row())->number;
case 1:
return list.at(index.row())->date;
case 2:
return list.at(index.row())->depth;
case 3:
return list.at(index.row())->duration;
case 4:
return list.at(index.row())->divemaster;
case 5:
return list.at(index.row())->buddy;
case 6:
return list.at(index.row())->location;
}
return QVariant();
}
bool TablePrintModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid()) {
if (role == Qt::DisplayRole) {
switch (index.column()) {
case 0:
list.at(index.row())->number = value.toString();
case 1:
list.at(index.row())->date = value.toString();
case 2:
list.at(index.row())->depth = value.toString();
case 3:
list.at(index.row())->duration = value.toString();
case 4:
list.at(index.row())->divemaster = value.toString();
case 5:
list.at(index.row())->buddy = value.toString();
case 6:
list.at(index.row())->location = value.toString();
}
return true;
}
if (role == Qt::BackgroundRole) {
list.at(index.row())->colorBackground = value.value<unsigned int>();
return true;
}
}
return false;
}
int TablePrintModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return rows;
}
int TablePrintModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return columns;
}

View file

@ -230,4 +230,45 @@ public:
YearlyStatisticsModel(QObject* parent = 0); YearlyStatisticsModel(QObject* parent = 0);
void update_yearly_stats(); void update_yearly_stats();
}; };
/* TablePrintModel:
* for now we use a blank table model with row items TablePrintItem.
* these are pretty much the same as DiveItem, but have color
* properties, as well. perhaps later one a more unified model has to be
* considered, but the current TablePrintModel idea has to be extended
* to support variadic column lists and column list orders that can
* be controlled by the user.
*/
struct TablePrintItem {
QString number;
QString date;
QString depth;
QString duration;
QString divemaster;
QString buddy;
QString location;
unsigned int colorBackground;
};
class TablePrintModel : public QAbstractTableModel
{
Q_OBJECT
private:
QList<struct TablePrintItem *> list;
public:
~TablePrintModel();
TablePrintModel();
int rows, columns;
void insertRow(int index = -1);
void callReset();
QVariant data(const QModelIndex &index, int role) const;
bool setData(const QModelIndex &index, const QVariant &value, int role);
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
};
#endif #endif

View file

@ -1,9 +1,10 @@
#include <QtCore/qmath.h>
#include <QDebug> #include <QDebug>
#include <QPainter> #include <QPainter>
#include <QDesktopWidget> #include <QDesktopWidget>
#include <QApplication> #include <QApplication>
#include <QTextDocument> #include <QTableView>
#include <QAbstractTextDocumentLayout> #include <QHeaderView>
#include "mainwindow.h" #include "mainwindow.h"
#include "profilegraphics.h" #include "profilegraphics.h"
#include "printlayout.h" #include "printlayout.h"
@ -21,8 +22,6 @@ struct options {
}; };
*/ */
#define TABLE_PRINT_COL 7
PrintLayout::PrintLayout(PrintDialog *dialogPtr, QPrinter *printerPtr, struct options *optionsPtr) PrintLayout::PrintLayout(PrintDialog *dialogPtr, QPrinter *printerPtr, struct options *optionsPtr)
{ {
dialog = dialogPtr; dialog = dialogPtr;
@ -30,20 +29,21 @@ PrintLayout::PrintLayout(PrintDialog *dialogPtr, QPrinter *printerPtr, struct op
printOptions = optionsPtr; printOptions = optionsPtr;
// table print settings // table print settings
tableColumnNames.append(tr("Dive#")); tablePrintHeadingBackground = 0xffeeeeee;
tableColumnNames.append(tr("Date")); tablePrintColumnNames.append(tr("Dive#"));
tableColumnNames.append(tr("Depth")); tablePrintColumnNames.append(tr("Date"));
tableColumnNames.append(tr("Duration")); tablePrintColumnNames.append(tr("Depth"));
tableColumnNames.append(tr("Master")); tablePrintColumnNames.append(tr("Duration"));
tableColumnNames.append(tr("Buddy")); tablePrintColumnNames.append(tr("Master"));
tableColumnNames.append(tr("Location")); tablePrintColumnNames.append(tr("Buddy"));
tableColumnWidths.append("7"); tablePrintColumnNames.append(tr("Location"));
tableColumnWidths.append("10"); tablePrintColumnWidths.append(7);
tableColumnWidths.append("10"); tablePrintColumnWidths.append(10);
tableColumnWidths.append("10"); tablePrintColumnWidths.append(10);
tableColumnWidths.append("15"); tablePrintColumnWidths.append(10);
tableColumnWidths.append("15"); tablePrintColumnWidths.append(15);
tableColumnWidths.append("100"); tablePrintColumnWidths.append(15);
tablePrintColumnWidths.append(33);
} }
void PrintLayout::print() void PrintLayout::print()
@ -74,6 +74,10 @@ void PrintLayout::setup()
scaleX = (qreal)printerDpi/(qreal)screenDpiX; scaleX = (qreal)printerDpi/(qreal)screenDpiX;
scaleY = (qreal)printerDpi/(qreal)screenDpiY; scaleY = (qreal)printerDpi/(qreal)screenDpiY;
// a printer page scalled to screen DPI
scaledPageW = pageRect.width() / scaleX;
scaledPageH = pageRect.height() / scaleY;
} }
// experimental // experimental
@ -90,7 +94,7 @@ void PrintLayout::printSixDives() const
profile->clear(); profile->clear();
profile->setPrintMode(true, !printOptions->color_selected); profile->setPrintMode(true, !printOptions->color_selected);
QSize originalSize = profile->size(); QSize originalSize = profile->size();
profile->resize(pageRect.height()/scaleY, pageRect.width()/scaleX); profile->resize(scaledPageW, scaledPageH);
int i; int i;
struct dive *dive; struct dive *dive;
@ -122,127 +126,100 @@ void PrintLayout::printTwoDives() const
// nop // nop
} }
void PrintLayout::printTable() const void PrintLayout::printTable()
{ {
QTextDocument doc; // create and setup a table
QSizeF pageSize; QTableView table;
pageSize.setWidth(pageRect.width()); table.setAttribute(Qt::WA_DontShowOnScreen);
pageSize.setHeight(pageRect.height()); table.setSelectionMode(QAbstractItemView::NoSelection);
doc.documentLayout()->setPaintDevice(printer); table.setFocusPolicy(Qt::NoFocus);
doc.setPageSize(pageSize); table.horizontalHeader()->setVisible(false);
table.horizontalHeader()->setResizeMode(QHeaderView::Fixed);
table.verticalHeader()->setVisible(false);
table.verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
table.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
table.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// fit table to one page initially
table.resize(scaledPageW, scaledPageH);
QString styleSheet( // create and fill a table model
"<style type='text/css'>" TablePrintModel model;
"table {"
" border-width: 1px;"
" border-style: solid;"
" border-color: #999999;"
"}"
"th {"
" background-color: #eeeeee;"
" font-size: small;"
" padding: 3px 5px 3px 5px;"
"}"
"td {"
" font-size: small;"
" padding: 3px 5px 3px 5px;"
"}"
"</style>"
);
// setDefaultStyleSheet() doesn't work here?
const QString heading(insertTableHeadingRow());
const QString lineBreak("<br>");
QString htmlText = styleSheet + "<table cellspacing='0' width='100%'>";
QString htmlTextPrev;
int pageCountNew = 1, pageCount = 0, lastPageWithHeading = 0;
bool insertHeading = true;
int i;
struct dive *dive; struct dive *dive;
int i, row = 0;
addTablePrintHeadingRow(&model, row); // add one heading row
row++;
for_each_dive(i, dive) { for_each_dive(i, dive) {
if (!dive->selected && printOptions->print_selected) if (!dive->selected && printOptions->print_selected)
continue; continue;
if (insertHeading) { addTablePrintDataRow(&model, row, dive);
htmlTextPrev = htmlText; row++;
htmlText += heading; }
doc.setHtml(htmlText); table.setModel(&model); // set model to table
pageCount = doc.pageCount(); // resize columns to percentages from page width
// prevent adding two headings on the same page for (int i = 0; i < model.columns; i++) {
if (pageCount == lastPageWithHeading) { int pw = qCeil((qreal)(tablePrintColumnWidths.at(i) * table.width()) / 100);
htmlText = htmlTextPrev; table.horizontalHeader()->resizeSection(i, pw);
// add line breaks until a new page is reached }
while (pageCount == lastPageWithHeading) { // reset the model at this point
htmlTextPrev = htmlText; model.callReset();
htmlText += lineBreak;
doc.setHtml(htmlText); // a list of vertical offsets where pages begin and some helpers
pageCount = doc.pageCount(); QList<unsigned int> pageIndexes;
} pageIndexes.append(0);
// revert last line break from the new page and add heading int tableHeight = 0, rowH = 0, accH = 0;
htmlText = htmlTextPrev;
htmlText += heading; // process all rows
} for (int i = 0; i < model.rows; i++) {
insertHeading = false; rowH = table.rowHeight(i);
lastPageWithHeading = pageCount; accH += rowH;
} if (accH > scaledPageH) { // push a new page index and add a heading
htmlTextPrev = htmlText; pageIndexes.append(pageIndexes.last() + (accH - rowH));
htmlText += insertTableDataRow(dive); addTablePrintHeadingRow(&model, i);
doc.setHtml(htmlText); accH = 0;
pageCount = pageCountNew;
pageCountNew = doc.pageCount();
// if the page count increases revert and add heading instead
if (pageCountNew > pageCount) {
htmlText = htmlTextPrev;
insertHeading = true;
i--; i--;
} }
tableHeight += rowH;
}
pageIndexes.append(pageIndexes.last() + accH);
// resize the whole widget so that it can be rendered
table.resize(scaledPageW, tableHeight);
// attach a painter and render pages by using pageIndexes
QPainter painter(printer);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
painter.scale(scaleX, scaleY);
for (int i = 0; i < pageIndexes.size() - 1; i++) {
if (i > 0)
printer->newPage();
QRegion region(0, pageIndexes.at(i) - 1,
table.width(),
pageIndexes.at(i + 1) - pageIndexes.at(i) + 2);
table.render(&painter, QPoint(0, 0), region);
} }
htmlText += "</table>";
doc.setHtml(htmlText);
doc.print(printer);
} }
QString PrintLayout::insertTableHeadingRow() const void PrintLayout::addTablePrintDataRow(TablePrintModel *model, int row, struct dive *dive) const
{ {
int i;
QString ret("<tr>");
for (i = 0; i < TABLE_PRINT_COL; i++)
ret += insertTableHeadingCol(i);
ret += "</tr>";
return ret;
}
QString PrintLayout::insertTableHeadingCol(int col) const
{
QString ret("<th align='left' width='");
ret += tableColumnWidths.at(col);
ret += "%'>";
ret += tableColumnNames.at(col);
ret += "</th>";
return ret;
}
QString PrintLayout::insertTableDataRow(struct dive *dive) const
{
// use the DiveItem class
struct DiveItem di; struct DiveItem di;
di.dive = dive; di.dive = dive;
model->insertRow();
// fill row model->setData(model->index(row, 0), QString::number(dive->number), Qt::DisplayRole);
QString ret("<tr>"); model->setData(model->index(row, 1), di.displayDate(), Qt::DisplayRole);
ret += insertTableDataCol(QString::number(dive->number)); model->setData(model->index(row, 2), di.displayDepth(), Qt::DisplayRole);
ret += insertTableDataCol(di.displayDate()); model->setData(model->index(row, 3), di.displayDuration(), Qt::DisplayRole);
ret += insertTableDataCol(di.displayDepth()); model->setData(model->index(row, 4), dive->divemaster, Qt::DisplayRole);
ret += insertTableDataCol(di.displayDuration()); model->setData(model->index(row, 5), dive->buddy, Qt::DisplayRole);
ret += insertTableDataCol(dive->divemaster); model->setData(model->index(row, 6), dive->location, Qt::DisplayRole);
ret += insertTableDataCol(dive->buddy);
ret += insertTableDataCol(dive->location);
ret += "</tr>";
return ret;
} }
QString PrintLayout::insertTableDataCol(QString data) const void PrintLayout::addTablePrintHeadingRow(TablePrintModel *model, int row) const
{ {
return "<td>" + data + "</td>"; model->insertRow(row);
for (int i = 0; i < model->columns; i++) {
model->setData(model->index(row, i), tablePrintColumnNames.at(i), Qt::DisplayRole);
model->setData(model->index(row, i), tablePrintHeadingBackground, Qt::BackgroundRole);
}
} }
// experimental // experimental

View file

@ -1,10 +1,13 @@
#ifndef PRINTLAYOUT_H #ifndef PRINTLAYOUT_H
#define PRINTLAYOUT_H #define PRINTLAYOUT_H
#include <QObject>
#include <QPrinter> #include <QPrinter>
#include <QStringList> #include <QList>
class PrintDialog; class PrintDialog;
class TablePrintModel;
struct dive;
class PrintLayout : public QObject { class PrintLayout : public QObject {
Q_OBJECT Q_OBJECT
@ -19,21 +22,21 @@ private:
struct options *printOptions; struct options *printOptions;
QPainter *painter; QPainter *painter;
int screenDpiX, screenDpiY, printerDpi; int screenDpiX, screenDpiY, printerDpi, scaledPageW, scaledPageH;
qreal scaleX, scaleY; qreal scaleX, scaleY;
QRect pageRect; QRect pageRect;
QStringList tableColumnNames; QList<QString> tablePrintColumnNames;
QStringList tableColumnWidths; QList<unsigned int> tablePrintColumnWidths;
unsigned int tablePrintHeadingBackground;
void setup(); void setup();
void printSixDives() const; void printSixDives() const;
void printTwoDives() const; void printTwoDives() const;
void printTable() const; void printTable();
QString insertTableHeadingRow() const; void addTablePrintDataRow(TablePrintModel *model, int row, struct dive *dive) const;
QString insertTableHeadingCol(int) const; void addTablePrintHeadingRow(TablePrintModel *model, int row) const;
QString insertTableDataRow(struct dive *) const;
QString insertTableDataCol(QString) const;
QPixmap convertPixmapToGrayscale(QPixmap) const; QPixmap convertPixmapToGrayscale(QPixmap) const;
}; };