Hi--
I'm trying to create diagonal table headers. So far, it's kind of working, but I'm running into what might be deep problems. My goal is to create a table that looks like this:
Screen Shot 2013-09-07 at 8.36.15 AM.jpg
So far, I have subclassed the header and added a custom delegate, getting me here:
Screen Shot 2013-09-08 at 1.36.49 PM.jpg
The diagonal and text elements are in the right spot, but they're being drawn on top of by other paint commands or are being stopped by the widgets boundaries.
Questions:
1) How can I expand the header boundaries so that my diagonal lines don't get truncated?
2) How can I draw the diagonal lines on top of the table, instead of having them be draw on top of?
diagonal_table.cpp
#include "diagonal_table.h"
#include <qnamespace.h>
#include <Qt>
{
DiagonalHeaderView *horizontalDiagonalHeader = new DiagonalHeaderView(Qt::Horizontal, this);
DiagonalHeaderView *verticalDiagonalHeader = new DiagonalHeaderView(Qt::Vertical, this);
setHorizontalHeader(horizontalDiagonalHeader);
setVerticalHeader(verticalDiagonalHeader);
setItemDelegate(new MyDelegate(this));
setShowGrid(false);
}
DiagonalHeaderView
::DiagonalHeaderView(Qt
::Orientation orientation,
QWidget *parent
) : QHeaderView(orientation, parent
){
}
void DiagonalHeaderView
::paintSection(QPainter *painter,
const QRect &rect,
int logicalIndex
) const {
if (!rect.isValid())
return;
initStyleOption(&opt);
opt.text = model()->headerData(logicalIndex, orientation(), Qt::DisplayRole).toString();
// the section position
int visual = visualIndex(logicalIndex);
Q_ASSERT(visual != -1);
if (count() == 1)
else if (visual == 0)
else if (visual == count() - 1)
else
opt.orientation = orientation();
// Get rotation, and wrap to (-180, 180]
double rotation = model()->headerData(logicalIndex, orientation(), DiagonalTable::RotationRole).toFloat();
while (rotation > 180)
rotation -=360;
while (rotation <= -180)
rotation +=360;
int separatorLength;
int sumLinePositionX;
int sumLinePositionY;
int textOffsetX;
int textOffsetY;
int textWidth = opt.fontMetrics.width(opt.text);
int textHeight = opt.fontMetrics.height();
switch (orientation()) {
case Qt::Horizontal:
{
// Cast parent so that we can use its methods
DiagonalTable * diagonalTable = dynamic_cast<DiagonalTable *>(this->parent());
if (diagonalTable == NULL)
Q_ASSERT(0);
// Calculate the rectangle for the box in which we will draw
int firstRow = model()->headerData(logicalIndex, orientation(),DiagonalTable::RowRole).toInt();
sumLinePositionX = rect.bottomLeft().x();
sumLinePositionY = rect.bottomLeft().y();
for (int i=0; i<firstRow; i++) {
int rowHeight = diagonalTable->rowHeight(i+1);
sumLinePositionY += rowHeight;
}
textOffsetX = rect.width()/2 + textHeight/2;
textOffsetY = 0;
}
break;
case Qt::Vertical:
{
if (rotation == 0) {
style
()->drawControl
(QStyle::CE_Header,
&opt, painter,
this);
} else {
// Cast parent so that we can use its methods
DiagonalTable * diagonalTable = dynamic_cast<DiagonalTable *>(this->parent());
if (diagonalTable == NULL)
Q_ASSERT(0);
// Calculate the rectangle for the box in which we will draw
int firstColumn = model()->headerData(logicalIndex, orientation(), DiagonalTable::ColumnRole).toInt();
sumLinePositionX = rect.bottomRight().x();
sumLinePositionY = rect.bottomRight().y();
for (int i=0; i<firstColumn; i++) {
int columnWidth = diagonalTable->columnWidth(i+1);
sumLinePositionX += columnWidth;
}
textOffsetX = 0;
textOffsetY = -rect.height()/2 + textHeight/2;
}
}
break;
}
// Paint the diagonal line
// Adjust for rotation
if (rotation > -90 && rotation < 90)
separatorLength = 30;
else
separatorLength = 90;
painter
->setRenderHint
(QPainter::Antialiasing);
painter->translate(sumLinePositionX, sumLinePositionY);
painter->rotate(rotation);
painter->drawLine(0, 0, separatorLength, 0);
// Paint text
painter->resetTransform();
// Adjust for rotation
if (rotation > -90 && rotation < 90) {
painter->translate(sumLinePositionX + textOffsetX, sumLinePositionY + textOffsetY);
painter->rotate(rotation);
painter->translate(2, 0); // Shift text up (along new axis)
} else {
painter->translate(sumLinePositionX + textOffsetX, sumLinePositionY + textOffsetY);
painter->rotate(rotation + 180); // Always draw text from left to right
painter->translate(-textWidth - 2, 0); // Back text up (along new axis)
}
painter->setFont(font()); // This font should be system dependent. Not sure how to get this.
painter->drawText(0, 0, opt.text);
}
{
int row = index.row();
int col = index.column();
int firstRow = index.model()->headerData(col, Qt::Horizontal,DiagonalTable::RowRole).toInt();
if (row < firstRow) {
} else {
int shift = 18; // This makes no sense yet, but for whatever reason the colors are shifted when drawn in the table
pen.
setColor(QColor(143+shift,
142+shift,
147+shift
));
pen.setWidth(1);
painter->setPen( pen );
painter->drawRect( option.rect.x()-1, option.rect.y()-1, option.rect.width(), option.rect.height() );
}
}
#include "diagonal_table.h"
#include <qnamespace.h>
#include <Qt>
DiagonalTable::DiagonalTable(QWidget *parent) : QTableView(parent)
{
DiagonalHeaderView *horizontalDiagonalHeader = new DiagonalHeaderView(Qt::Horizontal, this);
DiagonalHeaderView *verticalDiagonalHeader = new DiagonalHeaderView(Qt::Vertical, this);
setHorizontalHeader(horizontalDiagonalHeader);
setVerticalHeader(verticalDiagonalHeader);
setItemDelegate(new MyDelegate(this));
setShowGrid(false);
}
DiagonalHeaderView::DiagonalHeaderView(Qt::Orientation orientation, QWidget *parent) : QHeaderView(orientation, parent)
{
}
void DiagonalHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
{
if (!rect.isValid())
return;
QStyleOptionHeader opt;
initStyleOption(&opt);
opt.text = model()->headerData(logicalIndex, orientation(), Qt::DisplayRole).toString();
// the section position
int visual = visualIndex(logicalIndex);
Q_ASSERT(visual != -1);
if (count() == 1)
opt.position = QStyleOptionHeader::OnlyOneSection;
else if (visual == 0)
opt.position = QStyleOptionHeader::Beginning;
else if (visual == count() - 1)
opt.position = QStyleOptionHeader::End;
else
opt.position = QStyleOptionHeader::Middle;
opt.orientation = orientation();
// Get rotation, and wrap to (-180, 180]
double rotation = model()->headerData(logicalIndex, orientation(), DiagonalTable::RotationRole).toFloat();
while (rotation > 180)
rotation -=360;
while (rotation <= -180)
rotation +=360;
int separatorLength;
int sumLinePositionX;
int sumLinePositionY;
int textOffsetX;
int textOffsetY;
int textWidth = opt.fontMetrics.width(opt.text);
int textHeight = opt.fontMetrics.height();
switch (orientation()) {
case Qt::Horizontal:
{
// Cast parent so that we can use its methods
DiagonalTable * diagonalTable = dynamic_cast<DiagonalTable *>(this->parent());
if (diagonalTable == NULL)
Q_ASSERT(0);
// Calculate the rectangle for the box in which we will draw
int firstRow = model()->headerData(logicalIndex, orientation(),DiagonalTable::RowRole).toInt();
sumLinePositionX = rect.bottomLeft().x();
sumLinePositionY = rect.bottomLeft().y();
for (int i=0; i<firstRow; i++) {
int rowHeight = diagonalTable->rowHeight(i+1);
sumLinePositionY += rowHeight;
}
textOffsetX = rect.width()/2 + textHeight/2;
textOffsetY = 0;
}
break;
case Qt::Vertical:
{
if (rotation == 0) {
style()->drawControl(QStyle::CE_Header, &opt, painter, this);
} else {
// Cast parent so that we can use its methods
DiagonalTable * diagonalTable = dynamic_cast<DiagonalTable *>(this->parent());
if (diagonalTable == NULL)
Q_ASSERT(0);
// Calculate the rectangle for the box in which we will draw
int firstColumn = model()->headerData(logicalIndex, orientation(), DiagonalTable::ColumnRole).toInt();
sumLinePositionX = rect.bottomRight().x();
sumLinePositionY = rect.bottomRight().y();
for (int i=0; i<firstColumn; i++) {
int columnWidth = diagonalTable->columnWidth(i+1);
sumLinePositionX += columnWidth;
}
textOffsetX = 0;
textOffsetY = -rect.height()/2 + textHeight/2;
}
}
break;
}
// Paint the diagonal line
// Adjust for rotation
if (rotation > -90 && rotation < 90)
separatorLength = 30;
else
separatorLength = 90;
painter->setRenderHint(QPainter::Antialiasing);
painter->translate(sumLinePositionX, sumLinePositionY);
painter->rotate(rotation);
painter->drawLine(0, 0, separatorLength, 0);
// Paint text
painter->resetTransform();
// Adjust for rotation
if (rotation > -90 && rotation < 90) {
painter->translate(sumLinePositionX + textOffsetX, sumLinePositionY + textOffsetY);
painter->rotate(rotation);
painter->translate(2, 0); // Shift text up (along new axis)
} else {
painter->translate(sumLinePositionX + textOffsetX, sumLinePositionY + textOffsetY);
painter->rotate(rotation + 180); // Always draw text from left to right
painter->translate(-textWidth - 2, 0); // Back text up (along new axis)
}
painter->setFont(font()); // This font should be system dependent. Not sure how to get this.
painter->drawText(0, 0, opt.text);
}
void MyDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
int row = index.row();
int col = index.column();
int firstRow = index.model()->headerData(col, Qt::Horizontal,DiagonalTable::RowRole).toInt();
if (row < firstRow) {
} else {
QPen pen;
int shift = 18; // This makes no sense yet, but for whatever reason the colors are shifted when drawn in the table
pen.setColor(QColor(143+shift, 142+shift, 147+shift));
pen.setWidth(1);
painter->setPen( pen );
painter->drawRect( option.rect.x()-1, option.rect.y()-1, option.rect.width(), option.rect.height() );
QItemDelegate::paint( painter, option, index );
}
}
To copy to clipboard, switch view to plain text mode
diagonal_table.h
#ifndef DIAGONALTABLE_H
#define DIAGONALTABLE_H
#include <QWidget>
#include <QHeaderView>
#include <QItemDelegate>
#include <QPainter>
#include <QTableView>
{
Q_OBJECT
public:
enum {RotationRole = Qt::UserRole + 0,
RowRole = Qt::UserRole + 1,
ColumnRole = Qt::UserRole + 2};
public:
explicit DiagonalTable
(QWidget *parent
= 0);
~DiagonalTable();
};
public:
};
{
Q_OBJECT
public:
DiagonalHeaderView
(Qt
::Orientation orientation,
QWidget *parent
= 0);
~DiagonalHeaderView();
void paintSection
(QPainter *painter,
const QRect &rect,
int logicalIndex
) const;
};
#endif // DIAGONALTABLE_H
#ifndef DIAGONALTABLE_H
#define DIAGONALTABLE_H
#include <QWidget>
#include <QHeaderView>
#include <QItemDelegate>
#include <QPainter>
#include <QTableView>
class DiagonalTable : public QTableView
{
Q_OBJECT
public:
enum {RotationRole = Qt::UserRole + 0,
RowRole = Qt::UserRole + 1,
ColumnRole = Qt::UserRole + 2};
public:
explicit DiagonalTable(QWidget *parent = 0);
~DiagonalTable();
};
class MyDelegate : public QItemDelegate {
public:
MyDelegate( QObject *parent ) : QItemDelegate( parent ) { }
void paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const;
};
class DiagonalHeaderView : public QHeaderView
{
Q_OBJECT
public:
DiagonalHeaderView(Qt::Orientation orientation, QWidget *parent = 0);
~DiagonalHeaderView();
void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const;
};
#endif // DIAGONALTABLE_H
To copy to clipboard, switch view to plain text mode
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "diagonal_table.h"
#include <QStandardItemModel>
MainWindow
::MainWindow(QWidget *parent
) : ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->tableView->setModel(sim);
horizontalHeaderLabels << "Output channel" << "Channel type" << "Live view" << "Curve 1" << "Curve 2" << "Roll" << "Pitch" << "Yaw";
sim->setHorizontalHeaderLabels(horizontalHeaderLabels);
// Set horizontal header rotation
sim->setHeaderData(0, Qt::Horizontal, -150, DiagonalTable::RotationRole);
sim->setHeaderData(1, Qt::Horizontal, -150, DiagonalTable::RotationRole);
sim->setHeaderData(2, Qt::Horizontal, -150, DiagonalTable::RotationRole);
sim->setHeaderData(3, Qt::Horizontal, -45, DiagonalTable::RotationRole);
sim->setHeaderData(4, Qt::Horizontal, -45, DiagonalTable::RotationRole);
sim->setHeaderData(5, Qt::Horizontal, -45, DiagonalTable::RotationRole);
sim->setHeaderData(6, Qt::Horizontal, -45, DiagonalTable::RotationRole);
sim->setHeaderData(7, Qt::Horizontal, -45, DiagonalTable::RotationRole);
// Set row for horizontal header. All cella above this point will be empty
sim->setHeaderData(0, Qt::Horizontal, 1, DiagonalTable::RowRole);
sim->setHeaderData(1, Qt::Horizontal, 1, DiagonalTable::RowRole);
sim->setHeaderData(2, Qt::Horizontal, 1, DiagonalTable::RowRole);
verticalHeaderLabels << "Live view" << "Channel 1" << "Channel 2" << "Channel 3"
<< "Channel 4" << "Channel 5" << "Channel 6";
sim->setVerticalHeaderLabels(verticalHeaderLabels);
// Set vertical header rotation
sim->setHeaderData(0, Qt::Vertical, -150, DiagonalTable::RotationRole);
// Set column for vertical header. All cells to the left of this point will be empty
sim->setHeaderData(0, Qt::Vertical, 3, DiagonalTable::ColumnRole);
for (int j=0; j < sim->rowCount(); j++) {
for (int i=0; i < sim->columnCount(); i++) {
int firstRow = sim->headerData(i, Qt::Horizontal,DiagonalTable::RowRole).toInt();
if (j < firstRow)
continue;
}
}
ui->tableView->resizeColumnsToContents();
}
MainWindow::~MainWindow()
{
delete ui;
}
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "diagonal_table.h"
#include <QStandardItemModel>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QStandardItemModel *sim = new QStandardItemModel(0,0,ui->tableView);
ui->tableView->setModel(sim);
QStringList horizontalHeaderLabels;
horizontalHeaderLabels << "Output channel" << "Channel type" << "Live view" << "Curve 1" << "Curve 2" << "Roll" << "Pitch" << "Yaw";
sim->setHorizontalHeaderLabels(horizontalHeaderLabels);
// Set horizontal header rotation
sim->setHeaderData(0, Qt::Horizontal, -150, DiagonalTable::RotationRole);
sim->setHeaderData(1, Qt::Horizontal, -150, DiagonalTable::RotationRole);
sim->setHeaderData(2, Qt::Horizontal, -150, DiagonalTable::RotationRole);
sim->setHeaderData(3, Qt::Horizontal, -45, DiagonalTable::RotationRole);
sim->setHeaderData(4, Qt::Horizontal, -45, DiagonalTable::RotationRole);
sim->setHeaderData(5, Qt::Horizontal, -45, DiagonalTable::RotationRole);
sim->setHeaderData(6, Qt::Horizontal, -45, DiagonalTable::RotationRole);
sim->setHeaderData(7, Qt::Horizontal, -45, DiagonalTable::RotationRole);
// Set row for horizontal header. All cella above this point will be empty
sim->setHeaderData(0, Qt::Horizontal, 1, DiagonalTable::RowRole);
sim->setHeaderData(1, Qt::Horizontal, 1, DiagonalTable::RowRole);
sim->setHeaderData(2, Qt::Horizontal, 1, DiagonalTable::RowRole);
QStringList verticalHeaderLabels;
verticalHeaderLabels << "Live view" << "Channel 1" << "Channel 2" << "Channel 3"
<< "Channel 4" << "Channel 5" << "Channel 6";
sim->setVerticalHeaderLabels(verticalHeaderLabels);
// Set vertical header rotation
sim->setHeaderData(0, Qt::Vertical, -150, DiagonalTable::RotationRole);
// Set column for vertical header. All cells to the left of this point will be empty
sim->setHeaderData(0, Qt::Vertical, 3, DiagonalTable::ColumnRole);
for (int j=0; j < sim->rowCount(); j++) {
for (int i=0; i < sim->columnCount(); i++) {
int firstRow = sim->headerData(i, Qt::Horizontal,DiagonalTable::RowRole).toInt();
if (j < firstRow)
continue;
sim->setItem(j,i,new QStandardItem("-1.00"));
}
}
ui->tableView->resizeColumnsToContents();
}
MainWindow::~MainWindow()
{
delete ui;
}
To copy to clipboard, switch view to plain text mode
Bookmarks