[PATCH 3/4] Print: provide means to print profile tables

Lubomir I. Ivanov neolit123 at gmail.com
Thu Oct 3 07:50:40 UTC 2013


From: "Lubomir I. Ivanov" <neolit123 at gmail.com>

This patch adds a couple of classes and some other modifications
in PrintLayout that handle the printing of tables under a profile.

models.h : ProfilePrintModel
The class uses a 'struct *dive' to output all required data
for a certain dive at specific rows and columns. It also handles
font formatting and text alignment.

modeldelagatates.h : ProfilePrintDelegate
The class is used only for drawing a custom grid for profile tables.

PrintLayout::createProfileTable()
The function is used to create and setup the profile table object

PrintLayout::printProfileDives()
The function now has correct padding of dive profiles on a page
and also the printing of actual tables below them.

Signed-off-by: Lubomir I. Ivanov <neolit123 at gmail.com>
---
 qt-ui/modeldelegates.cpp |  27 +++++++
 qt-ui/modeldelegates.h   |  12 +++
 qt-ui/models.cpp         | 197 +++++++++++++++++++++++++++++++++++++++++++++++
 qt-ui/models.h           |  20 +++++
 qt-ui/printlayout.cpp    | 172 +++++++++++++++++++++++++++++++++--------
 qt-ui/printlayout.h      |   8 +-
 6 files changed, 403 insertions(+), 33 deletions(-)

diff --git a/qt-ui/modeldelegates.cpp b/qt-ui/modeldelegates.cpp
index 6141f79..e80afab 100644
--- a/qt-ui/modeldelegates.cpp
+++ b/qt-ui/modeldelegates.cpp
@@ -300,3 +300,30 @@ void AirTypesDelegate::setModelData(QWidget* editor, QAbstractItemModel* model,
 AirTypesDelegate::AirTypesDelegate(QObject* parent) : ComboBoxDelegate(airTypes(), parent)
 {
 }
+
+ProfilePrintDelegate::ProfilePrintDelegate(QObject *parent)
+	: QStyledItemDelegate(parent)
+{
+}
+
+/* this method overrides the default table drawing method and places grid lines only at certain rows and columns */
+void ProfilePrintDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+	const QRect rect(option.rect);
+	const int row = index.row();
+	const int col = index.column();
+
+	// grid color
+	painter->setPen(QPen(QColor(0xff999999)));
+	// top line
+	if (row == 2 || row == 3 || row == 10 || col == 3 || col == 4)
+		painter->drawLine(rect.topLeft(), rect.topRight());
+	if (row > 1 && row < 10) {
+		// left line - draw always for these rows
+		painter->drawLine(rect.topLeft(), rect.bottomLeft());
+		// "fix" for missing (?) right line after col 5
+		if (col > 5 || (col > 4 && row == 2))
+			painter->drawLine(rect.topRight(), rect.bottomRight());
+	}
+	QStyledItemDelegate::paint(painter, option, index);
+}
diff --git a/qt-ui/modeldelegates.h b/qt-ui/modeldelegates.h
index a873e06..a16e472 100644
--- a/qt-ui/modeldelegates.h
+++ b/qt-ui/modeldelegates.h
@@ -3,6 +3,7 @@
 
 #include <QStyledItemDelegate>
 class QComboBox;
+class QPainter;
 
 class StarWidgetsDelegate : public QStyledItemDelegate {
 	Q_OBJECT
@@ -60,4 +61,15 @@ public slots:
 	void revertModelData(QWidget* widget, QAbstractItemDelegate::EndEditHint hint);
 };
 
+/* ProfilePrintDelagate:
+ * this delegate is used to modify the look of the table that is printed
+ * bellow profiles.
+ */
+class ProfilePrintDelegate : public QStyledItemDelegate
+{
+public:
+	explicit ProfilePrintDelegate(QObject *parent = 0);
+	void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
+};
+
 #endif
diff --git a/qt-ui/models.cpp b/qt-ui/models.cpp
index 9446b7d..6831b92 100644
--- a/qt-ui/models.cpp
+++ b/qt-ui/models.cpp
@@ -1567,3 +1567,200 @@ int TablePrintModel::columnCount(const QModelIndex &parent) const
 	Q_UNUSED(parent);
 	return columns;
 }
+
+/*#################################################################
+ * #
+ * #	Profile Print Model
+ * #
+ * ################################################################
+ */
+
+ProfilePrintModel::ProfilePrintModel(QObject *parent)
+{
+}
+
+/* this is just a helper function to truncate C strings near 'maxlen' characters
+ * by finding word bounderies and adding '...' at the end of the truncated string.
+ * not really optimal for all languages!
+ */
+QString ProfilePrintModel::truncateString(char *str, const int maxlen) const
+{
+	if (!str)
+		return QString("");
+	QString trunc = QString(str);
+	const int len = trunc.length();
+	for (int i = 0; i < len; i++) {
+		char c = trunc.at(i).toAscii();
+		if (c == ' ' || c == '\n' || c == '\t') {
+			if (i > maxlen) {
+				trunc = trunc.left(i) + QString("...");
+				break;
+			}
+		}
+	}
+	return trunc;
+}
+
+void ProfilePrintModel::setDive(struct dive *divePtr)
+{
+	dive = divePtr;
+	// reset();
+}
+
+int ProfilePrintModel::rowCount(const QModelIndex &parent) const
+{
+	return 11;
+}
+
+int ProfilePrintModel::columnCount(const QModelIndex &parent) const
+{
+	return 7;
+}
+
+QVariant ProfilePrintModel::data(const QModelIndex &index, int role) const
+{
+	const int row = index.row();
+	const int col = index.column();
+
+	switch (role) {
+	case Qt::DisplayRole: {
+		struct DiveItem di;
+		di.dive = dive;
+		QString unit;
+		char buf[80];
+		const QString empty = QString("");
+		const QString unknown = QString(tr("unknown"));
+
+		// dive# + date, depth, location, duration
+		if (row == 0) {
+			if (col == 0)
+				return QString(tr("Dive #%1 - %2")).arg(dive->number).arg(di.displayDate());
+			if (col == 5) {
+				unit = (get_units()->length == units::METERS) ? "m" : "ft";
+				return QString(tr("Max depth: %1 %2")).arg(di.displayDepth()).arg(unit);
+			}
+		}
+		if (row == 1) {
+			if (col == 0)
+				return truncateString(dive->location, 32);
+			if (col == 5)
+				return QString(tr("Duration: %1 min")).arg(di.displayDuration());
+		}
+		// cylinder headings
+		if (row == 2) {
+			if (col == 0)
+				return QString(tr("Cylinder"));
+			if (col == 1)
+				return QString(tr("Gasmix"));
+			if (col == 2)
+				return QString(tr("Gas Used"));
+		}
+		// cylinder data
+		if (row > 2 && row < 10 && row - 3 < MAX_CYLINDERS) {
+			cylinder_t *cyl = &dive->cylinder[row - 3];
+			if (cyl->type.description) { // how do we check if a cylinder is added?
+				if (col == 0) {
+					if (cyl->type.description[0] != '\0')
+						return QString(cyl->type.description);
+					return unknown;
+				}
+				if (col == 1) {
+					get_gas_string(cyl->gasmix.o2.permille, cyl->gasmix.he.permille, buf, sizeof(buf));
+					return QString(buf);
+				}
+				if (col == 2) {
+					return get_cylinder_used_gas_string(cyl, true);
+				}
+			}
+		}
+		// dive notes
+		if (row == 10 && col == 0)
+			return truncateString(dive->notes, 64);
+		// sac, cns, otu - headings
+		if (col == 3) {
+			if (row == 2)
+				return QString(tr("SAC"));
+			if (row == 4)
+				return QString(tr("Max. CNS"));
+			if (row == 6)
+				return QString(tr("OTU"));
+		}
+		// sac, cns, otu - data
+		if (col == 4) {
+			if (row == 2)
+				return di.displaySac();
+			if (row == 4)
+				return QString::number(dive->maxcns);
+			if (row == 6)
+				return QString::number(dive->otu);
+		}
+		// weights heading
+		if (row == 2 && col == 5)
+			return QString(tr("Weights"));
+		// total weight
+		if (row == 9) {
+			weight_t tw = { total_weight(dive) };
+			if (tw.grams) {
+				if (col == 5)
+					return QString("Total weight");
+				if (col == 6)
+					return get_weight_string(tw, true);
+			}
+		}
+		// weight data
+		if (row > 2 && row < 10 && row - 3 < MAX_WEIGHTSYSTEMS) {
+			weightsystem_t *ws = &dive->weightsystem[row - 3];
+			if (ws->weight.grams) {
+				if (col == 5) {
+					if (ws->description && ws->description[0] != '\0')
+						return QString(ws->description);
+					return unknown;
+				}
+				if (col == 6) {
+					return get_weight_string(ws->weight, true);
+				}
+			}
+		}
+		return empty;
+	}
+	case Qt::FontRole: {
+		QFont font;
+		const int baseSize = 8;
+		// dive #
+		if (row == 0 && col == 0) {
+			font.setBold(true);
+			font.setPixelSize(baseSize + 1);
+			return QVariant::fromValue(font);
+		}
+		// dive location
+		if (row == 1 && col == 0) {
+			font.setPixelSize(baseSize);
+			font.setBold(true);
+			return QVariant::fromValue(font);
+		}
+		// depth/duration
+		if ((row == 0 || row == 1) && col == 5) {
+			font.setPixelSize(baseSize);
+			return QVariant::fromValue(font);
+		}
+		// notes
+		if (row == 9 && col == 0) {
+			font.setPixelSize(baseSize + 1);
+			return QVariant::fromValue(font);
+		}
+		font.setPixelSize(baseSize);
+		return QVariant::fromValue(font);
+	}
+	case Qt::TextAlignmentRole: {
+		unsigned int align = Qt::AlignCenter;
+		// dive #, location, notes
+		if ((row < 2 || row == 10) && col == 0)
+			align = Qt::AlignLeft | Qt::AlignVCenter;
+		// depth, duration
+		if (row < 2 && col == 5)
+			align = Qt::AlignRight | Qt::AlignVCenter;
+		return QVariant::fromValue(align);
+	}
+	} // switch (role)
+	return QVariant();
+}
diff --git a/qt-ui/models.h b/qt-ui/models.h
index 2a9945a..75c3e08 100644
--- a/qt-ui/models.h
+++ b/qt-ui/models.h
@@ -274,4 +274,24 @@ public:
 	int columnCount(const QModelIndex &parent) const;
 };
 
+/* ProfilePrintModel:
+ * this model is used when printing a data table under a profile. it requires
+ * some exact usage of setSpan(..) on the target QTableView widget.
+ */
+class ProfilePrintModel : public QAbstractTableModel
+{
+	Q_OBJECT
+
+private:
+	struct dive *dive;
+	QString truncateString(char *str, const int maxlen) const;
+
+public:
+	ProfilePrintModel(QObject *parent = 0);
+	int rowCount(const QModelIndex &parent = QModelIndex()) const;
+	int columnCount(const QModelIndex &parent = QModelIndex()) const;
+	QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+	void setDive(struct dive *divePtr);
+};
+
 #endif
diff --git a/qt-ui/printlayout.cpp b/qt-ui/printlayout.cpp
index 3d09376..4f741d6 100644
--- a/qt-ui/printlayout.cpp
+++ b/qt-ui/printlayout.cpp
@@ -11,22 +11,12 @@
 #include "../dive.h"
 #include "../display.h"
 #include "models.h"
-
-/*
-struct options {
-	enum { PRETTY, TABLE, TWOPERPAGE } type;
-	int print_selected;
-	int color_selected;
-	bool notes_up;
-	int profile_height, notes_height, tanks_height;
-};
-*/
+#include "modeldelegates.h"
 
 PrintLayout::PrintLayout(PrintDialog *dialogPtr, QPrinter *printerPtr, struct options *optionsPtr)
 {
 	dialog = dialogPtr;
 	printer = printerPtr;
-	painter = NULL;
 	printOptions = optionsPtr;
 
 	// table print settings
@@ -45,6 +35,27 @@ PrintLayout::PrintLayout(PrintDialog *dialogPtr, QPrinter *printerPtr, struct op
 	tablePrintColumnWidths.append(15);
 	tablePrintColumnWidths.append(15);
 	tablePrintColumnWidths.append(33);
+	// profile print settings
+	const int dw = 15; // base percentage
+	profilePrintColumnWidths.append(dw);
+	profilePrintColumnWidths.append(dw);
+	profilePrintColumnWidths.append(dw);
+	profilePrintColumnWidths.append(dw);
+	profilePrintColumnWidths.append(dw - 5);
+	profilePrintColumnWidths.append(dw + 5);
+	profilePrintColumnWidths.append(dw - 5); // fit to 100%
+	const int sr = 8; // smallest row height in pixels
+	profilePrintRowHeights.append(sr + 2);
+	profilePrintRowHeights.append(sr + 7);
+	profilePrintRowHeights.append(sr);
+	profilePrintRowHeights.append(sr);
+	profilePrintRowHeights.append(sr);
+	profilePrintRowHeights.append(sr);
+	profilePrintRowHeights.append(sr);
+	profilePrintRowHeights.append(sr);
+	profilePrintRowHeights.append(sr);
+	profilePrintRowHeights.append(sr);
+	profilePrintRowHeights.append(sr + 12);
 }
 
 void PrintLayout::print()
@@ -81,14 +92,25 @@ void PrintLayout::setup()
 	scaledPageH = pageRect.height() / scaleY;
 }
 
+/* the used formula here is:
+ * s = (S - (n - 1) * p) / n
+ * where:
+ * s is the length of a single element (unknown)
+ * S is the total available length
+ * n is the number of elements to fit
+ * p is the padding between elements
+ */
+#define ESTIMATE_DIVE_DIM(S, n, p) \
+	 ((S) - ((n) - 1) * (p)) / (n);
+
 void PrintLayout::printProfileDives(int divesPerRow, int divesPerColumn)
 {
 	// setup a painter
-	painter = new QPainter();
-	painter->begin(printer);
-	painter->setRenderHint(QPainter::Antialiasing);
-	painter->setRenderHint(QPainter::SmoothPixmapTransform);
-	painter->scale(scaleX, scaleY);
+	QPainter painter;
+	painter.begin(printer);
+	painter.setRenderHint(QPainter::Antialiasing);
+	painter.setRenderHint(QPainter::SmoothPixmapTransform);
+	painter.scale(scaleX, scaleY);
 
 	// setup the profile widget
 	ProfileGraphicsView *profile = mainWindow()->graphics();
@@ -101,15 +123,24 @@ void PrintLayout::printProfileDives(int divesPerRow, int divesPerColumn)
 		divesPerColumn = divesPerRow;
 		divesPerRow = swap;
 	}
-	// estimate profile and table height and resize the widget
-	const int scaledW = scaledPageW / divesPerColumn;
-	const int scaledH = scaledPageH / divesPerRow;
-	/* make the table 1/3 of the reserved height. potentially this could depend
-	 * on orientation and other parameters as well. */
-	const int tableHeight = scaledH / 3;
-	profile->resize(scaledW, scaledH - tableHeight);
-
-	// plot the dives at specific rows and columns
+	// padding in pixels between two dives. no padding if only one dive per page.
+	const int padDef = 20;
+	const int padW = (divesPerColumn < 2) ? 0 : padDef;
+	const int padH = (divesPerRow < 2) ? 0 : padDef;
+	// estimate dimensions for a single dive
+	const int scaledW = ESTIMATE_DIVE_DIM(scaledPageW, divesPerColumn, padW);
+	const int scaledH = ESTIMATE_DIVE_DIM(scaledPageH, divesPerRow, padH);
+	// padding in pixels between profile and table
+	const int padPT = 10;
+	// create a model and table
+	ProfilePrintModel model;
+	QTableView *table = createProfileTable(&model, scaledW);
+	// profilePrintTableMaxH updates after the table is created
+	const int tableH = profilePrintTableMaxH;
+	// resize the profile widget
+	profile->resize(scaledW, scaledH - tableH - padPT);
+
+	// plot the dives at specific rows and columns on the page
 	int i, row = 0, col = 0;
 	struct dive *dive;
 	for_each_dive(i, dive) {
@@ -123,23 +154,102 @@ void PrintLayout::printProfileDives(int divesPerRow, int divesPerColumn)
 				printer->newPage();
 			}
 		}
+		// draw a profile
 		profile->plot(dive, true);
-		QPixmap pm = QPixmap::grabWidget(profile);
-		painter->drawPixmap(scaledW * col, scaledH * row, pm);
-		/* TODO: table should be drawn here, preferably by another function */
+		QPixmap profilePm = QPixmap::grabWidget(profile); // Qt4
+		painter.drawPixmap((scaledW + padW) * col,
+		                   (scaledH + padH) * row,
+		                   profilePm);
+		// draw a table
+		model.setDive(dive);
+		QPixmap tablePm = QPixmap::grabWidget(table); // Qt4
+		painter.drawPixmap((scaledW + padW) * col,
+		                   (scaledH + padH) * row + (scaledH - tableH),
+		                   tablePm);
 		col++;
 	}
 
 	// cleanup
-	painter->end();
-	delete painter;
-	painter = NULL;
+	painter.end();
+	delete table;
 	profile->setPrintMode(false);
 	profile->resize(originalSize);
 	profile->clear();
 	profile->plot(current_dive, true);
 }
 
+/* we create a table that has a fixed height, but can stretch to fit certain width */
+QTableView *PrintLayout::createProfileTable(ProfilePrintModel *model, const int tableW)
+{
+	// setup a new table
+	QTableView *table = new QTableView();
+	QHeaderView *vHeader = table->verticalHeader();
+	QHeaderView *hHeader = table->horizontalHeader();
+	table->setAttribute(Qt::WA_DontShowOnScreen);
+	table->setSelectionMode(QAbstractItemView::NoSelection);
+	table->setFocusPolicy(Qt::NoFocus);
+	table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+	table->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+	hHeader->setVisible(false);
+	hHeader->setResizeMode(QHeaderView::Fixed);
+	vHeader->setVisible(false);
+	vHeader->setResizeMode(QHeaderView::Fixed);
+	// set the model
+	table->setModel(model);
+
+	/* setup cell span for the table using QTableView::setSpan().
+	 * changes made here reflect on ProfilePrintModel::data(). */
+	const int cols = model->columnCount();
+	const int rows = model->rowCount();
+	// top section
+	table->setSpan(0,  0, 1, cols - 2);
+	table->setSpan(1,  0, 1, cols - 2);
+	table->setSpan(10, 0, 1, cols);
+	table->setSpan(0,  5, 1, 2);
+	table->setSpan(1,  5, 1, 12);
+	// sac, cns, otu
+	table->setSpan(2, 3, 2, 1);
+	table->setSpan(4, 3, 2, 1);
+	table->setSpan(6, 3, 2, 1);
+	table->setSpan(8, 3, 2, 1);
+	table->setSpan(2, 4, 2, 1);
+	table->setSpan(4, 4, 2, 1);
+	table->setSpan(6, 4, 2, 1);
+	table->setSpan(8, 4, 2, 1);
+	// weights
+	table->setSpan(2, 5, 1, 2);
+
+	/* resize row heights to the 'profilePrintRowHeights' indexes.
+	 * profilePrintTableMaxH will then hold the table height. */
+	int i;
+	profilePrintTableMaxH = 0;
+	for (i = 0; i < rows; i++) {
+		int h = profilePrintRowHeights.at(i);
+		profilePrintTableMaxH += h;
+		vHeader->resizeSection(i, h);
+	}
+	// resize columns. columns widths are percentages from the table width.
+	int accW = 0;
+	for (i = 0; i < cols; i++) {
+		int pw = qCeil((qreal)(profilePrintColumnWidths.at(i) * tableW) / 100.0);
+		accW += pw;
+		if (i == cols - 1 && accW > tableW) /* adjust last column */
+			pw -= accW - tableW;
+		hHeader->resizeSection(i, pw);
+	}
+	// resize
+	table->resize(tableW, profilePrintTableMaxH);
+	// hide the grid and set a stylesheet
+	table->setItemDelegate(new ProfilePrintDelegate());
+	table->setShowGrid(false);
+	table->setStyleSheet(
+		"QTableView { border: none }"
+		"QTableView::item { border: 0px; padding-left: 2px; padding-right: 2px; }"
+	);
+	// return
+	return table;
+}
+
 void PrintLayout::printTable()
 {
 	// create and setup a table
diff --git a/qt-ui/printlayout.h b/qt-ui/printlayout.h
index 79c1d65..a1f3486 100644
--- a/qt-ui/printlayout.h
+++ b/qt-ui/printlayout.h
@@ -5,8 +5,10 @@
 #include <QPrinter>
 #include <QList>
 
+class QTableView;
 class PrintDialog;
 class TablePrintModel;
+class ProfilePrintModel;
 struct dive;
 
 class PrintLayout : public QObject {
@@ -19,7 +21,6 @@ public:
 private:
 	PrintDialog *dialog;
 	QPrinter *printer;
-	QPainter *painter;
 	struct options *printOptions;
 
 	int screenDpiX, screenDpiY, printerDpi, scaledPageW, scaledPageH;
@@ -27,11 +28,14 @@ private:
 	QRect pageRect;
 
 	QList<QString> tablePrintColumnNames;
-	QList<unsigned int> tablePrintColumnWidths;
 	unsigned int tablePrintHeadingBackground;
+	QList<unsigned int> tablePrintColumnWidths;
+	unsigned int profilePrintTableMaxH;
+	QList<unsigned int> profilePrintColumnWidths, profilePrintRowHeights;
 
 	void setup();
 	void printProfileDives(int divesPerRow, int divesPerColumn);
+	QTableView *createProfileTable(ProfilePrintModel *model, const int tableW);
 	void printTable();
 	void addTablePrintDataRow(TablePrintModel *model, int row, struct dive *dive) const;
 	void addTablePrintHeadingRow(TablePrintModel *model, int row) const;
-- 
1.7.11.msysgit.0



More information about the subsurface mailing list