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
Qt Code:
  1. #include "diagonal_table.h"
  2. #include <qnamespace.h>
  3.  
  4. #include <Qt>
  5.  
  6. DiagonalTable::DiagonalTable(QWidget *parent) : QTableView(parent)
  7. {
  8. DiagonalHeaderView *horizontalDiagonalHeader = new DiagonalHeaderView(Qt::Horizontal, this);
  9. DiagonalHeaderView *verticalDiagonalHeader = new DiagonalHeaderView(Qt::Vertical, this);
  10. setHorizontalHeader(horizontalDiagonalHeader);
  11. setVerticalHeader(verticalDiagonalHeader);
  12.  
  13. setItemDelegate(new MyDelegate(this));
  14. setShowGrid(false);
  15.  
  16. }
  17.  
  18. DiagonalHeaderView::DiagonalHeaderView(Qt::Orientation orientation, QWidget *parent) : QHeaderView(orientation, parent)
  19. {
  20.  
  21. }
  22.  
  23. void DiagonalHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
  24. {
  25. if (!rect.isValid())
  26. return;
  27.  
  28. initStyleOption(&opt);
  29.  
  30. opt.text = model()->headerData(logicalIndex, orientation(), Qt::DisplayRole).toString();
  31.  
  32.  
  33. // the section position
  34. int visual = visualIndex(logicalIndex);
  35. Q_ASSERT(visual != -1);
  36. if (count() == 1)
  37. opt.position = QStyleOptionHeader::OnlyOneSection;
  38. else if (visual == 0)
  39. opt.position = QStyleOptionHeader::Beginning;
  40. else if (visual == count() - 1)
  41. opt.position = QStyleOptionHeader::End;
  42. else
  43. opt.position = QStyleOptionHeader::Middle;
  44. opt.orientation = orientation();
  45.  
  46. // Get rotation, and wrap to (-180, 180]
  47. double rotation = model()->headerData(logicalIndex, orientation(), DiagonalTable::RotationRole).toFloat();
  48. while (rotation > 180)
  49. rotation -=360;
  50. while (rotation <= -180)
  51. rotation +=360;
  52.  
  53. int separatorLength;
  54. int sumLinePositionX;
  55. int sumLinePositionY;
  56. int textOffsetX;
  57. int textOffsetY;
  58. int textWidth = opt.fontMetrics.width(opt.text);
  59. int textHeight = opt.fontMetrics.height();
  60.  
  61. switch (orientation()) {
  62. case Qt::Horizontal:
  63. {
  64. // Cast parent so that we can use its methods
  65. DiagonalTable * diagonalTable = dynamic_cast<DiagonalTable *>(this->parent());
  66. if (diagonalTable == NULL)
  67. Q_ASSERT(0);
  68.  
  69. // Calculate the rectangle for the box in which we will draw
  70. int firstRow = model()->headerData(logicalIndex, orientation(),DiagonalTable::RowRole).toInt();
  71.  
  72. sumLinePositionX = rect.bottomLeft().x();
  73. sumLinePositionY = rect.bottomLeft().y();
  74.  
  75. for (int i=0; i<firstRow; i++) {
  76. int rowHeight = diagonalTable->rowHeight(i+1);
  77. sumLinePositionY += rowHeight;
  78. }
  79.  
  80. textOffsetX = rect.width()/2 + textHeight/2;
  81. textOffsetY = 0;
  82.  
  83. }
  84. break;
  85. case Qt::Vertical:
  86. {
  87. if (rotation == 0) {
  88. style()->drawControl(QStyle::CE_Header, &opt, painter, this);
  89. } else {
  90. // Cast parent so that we can use its methods
  91. DiagonalTable * diagonalTable = dynamic_cast<DiagonalTable *>(this->parent());
  92. if (diagonalTable == NULL)
  93. Q_ASSERT(0);
  94.  
  95. // Calculate the rectangle for the box in which we will draw
  96. int firstColumn = model()->headerData(logicalIndex, orientation(), DiagonalTable::ColumnRole).toInt();
  97.  
  98. sumLinePositionX = rect.bottomRight().x();
  99. sumLinePositionY = rect.bottomRight().y();
  100.  
  101. for (int i=0; i<firstColumn; i++) {
  102. int columnWidth = diagonalTable->columnWidth(i+1);
  103. sumLinePositionX += columnWidth;
  104. }
  105.  
  106. textOffsetX = 0;
  107. textOffsetY = -rect.height()/2 + textHeight/2;
  108. }
  109. }
  110. break;
  111. }
  112.  
  113. // Paint the diagonal line
  114. // Adjust for rotation
  115. if (rotation > -90 && rotation < 90)
  116. separatorLength = 30;
  117. else
  118. separatorLength = 90;
  119. painter->setRenderHint(QPainter::Antialiasing);
  120. painter->translate(sumLinePositionX, sumLinePositionY);
  121. painter->rotate(rotation);
  122. painter->drawLine(0, 0, separatorLength, 0);
  123.  
  124. // Paint text
  125. painter->resetTransform();
  126. // Adjust for rotation
  127. if (rotation > -90 && rotation < 90) {
  128. painter->translate(sumLinePositionX + textOffsetX, sumLinePositionY + textOffsetY);
  129. painter->rotate(rotation);
  130. painter->translate(2, 0); // Shift text up (along new axis)
  131. } else {
  132. painter->translate(sumLinePositionX + textOffsetX, sumLinePositionY + textOffsetY);
  133. painter->rotate(rotation + 180); // Always draw text from left to right
  134. painter->translate(-textWidth - 2, 0); // Back text up (along new axis)
  135. }
  136. painter->setFont(font()); // This font should be system dependent. Not sure how to get this.
  137. painter->drawText(0, 0, opt.text);
  138.  
  139. }
  140.  
  141.  
  142. void MyDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
  143. {
  144. int row = index.row();
  145. int col = index.column();
  146.  
  147. int firstRow = index.model()->headerData(col, Qt::Horizontal,DiagonalTable::RowRole).toInt();
  148. if (row < firstRow) {
  149. } else {
  150. QPen pen;
  151. int shift = 18; // This makes no sense yet, but for whatever reason the colors are shifted when drawn in the table
  152. pen.setColor(QColor(143+shift, 142+shift, 147+shift));
  153. pen.setWidth(1);
  154. painter->setPen( pen );
  155.  
  156. painter->drawRect( option.rect.x()-1, option.rect.y()-1, option.rect.width(), option.rect.height() );
  157. QItemDelegate::paint( painter, option, index );
  158. }
  159.  
  160. }
To copy to clipboard, switch view to plain text mode 

diagonal_table.h
Qt Code:
  1. #ifndef DIAGONALTABLE_H
  2. #define DIAGONALTABLE_H
  3. #include <QWidget>
  4. #include <QHeaderView>
  5. #include <QItemDelegate>
  6. #include <QPainter>
  7. #include <QTableView>
  8.  
  9. class DiagonalTable : public QTableView
  10. {
  11. Q_OBJECT
  12.  
  13. public:
  14. enum {RotationRole = Qt::UserRole + 0,
  15. RowRole = Qt::UserRole + 1,
  16. ColumnRole = Qt::UserRole + 2};
  17.  
  18. public:
  19. explicit DiagonalTable(QWidget *parent = 0);
  20. ~DiagonalTable();
  21.  
  22. };
  23.  
  24.  
  25. class MyDelegate : public QItemDelegate {
  26. public:
  27. MyDelegate( QObject *parent ) : QItemDelegate( parent ) { }
  28. void paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const;
  29. };
  30.  
  31. class DiagonalHeaderView : public QHeaderView
  32. {
  33. Q_OBJECT
  34.  
  35. public:
  36. DiagonalHeaderView(Qt::Orientation orientation, QWidget *parent = 0);
  37. ~DiagonalHeaderView();
  38. void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const;
  39.  
  40. };
  41.  
  42. #endif // DIAGONALTABLE_H
To copy to clipboard, switch view to plain text mode 

mainwindow.cpp
Qt Code:
  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3. #include "diagonal_table.h"
  4. #include <QStandardItemModel>
  5.  
  6. MainWindow::MainWindow(QWidget *parent) :
  7. QMainWindow(parent),
  8. ui(new Ui::MainWindow)
  9. {
  10. ui->setupUi(this);
  11.  
  12.  
  13. QStandardItemModel *sim = new QStandardItemModel(0,0,ui->tableView);
  14. ui->tableView->setModel(sim);
  15.  
  16. QStringList horizontalHeaderLabels;
  17. horizontalHeaderLabels << "Output channel" << "Channel type" << "Live view" << "Curve 1" << "Curve 2" << "Roll" << "Pitch" << "Yaw";
  18. sim->setHorizontalHeaderLabels(horizontalHeaderLabels);
  19.  
  20. // Set horizontal header rotation
  21. sim->setHeaderData(0, Qt::Horizontal, -150, DiagonalTable::RotationRole);
  22. sim->setHeaderData(1, Qt::Horizontal, -150, DiagonalTable::RotationRole);
  23. sim->setHeaderData(2, Qt::Horizontal, -150, DiagonalTable::RotationRole);
  24. sim->setHeaderData(3, Qt::Horizontal, -45, DiagonalTable::RotationRole);
  25. sim->setHeaderData(4, Qt::Horizontal, -45, DiagonalTable::RotationRole);
  26. sim->setHeaderData(5, Qt::Horizontal, -45, DiagonalTable::RotationRole);
  27. sim->setHeaderData(6, Qt::Horizontal, -45, DiagonalTable::RotationRole);
  28. sim->setHeaderData(7, Qt::Horizontal, -45, DiagonalTable::RotationRole);
  29.  
  30. // Set row for horizontal header. All cella above this point will be empty
  31. sim->setHeaderData(0, Qt::Horizontal, 1, DiagonalTable::RowRole);
  32. sim->setHeaderData(1, Qt::Horizontal, 1, DiagonalTable::RowRole);
  33. sim->setHeaderData(2, Qt::Horizontal, 1, DiagonalTable::RowRole);
  34.  
  35. QStringList verticalHeaderLabels;
  36. verticalHeaderLabels << "Live view" << "Channel 1" << "Channel 2" << "Channel 3"
  37. << "Channel 4" << "Channel 5" << "Channel 6";
  38. sim->setVerticalHeaderLabels(verticalHeaderLabels);
  39.  
  40. // Set vertical header rotation
  41. sim->setHeaderData(0, Qt::Vertical, -150, DiagonalTable::RotationRole);
  42.  
  43. // Set column for vertical header. All cells to the left of this point will be empty
  44. sim->setHeaderData(0, Qt::Vertical, 3, DiagonalTable::ColumnRole);
  45.  
  46. for (int j=0; j < sim->rowCount(); j++) {
  47. for (int i=0; i < sim->columnCount(); i++) {
  48. int firstRow = sim->headerData(i, Qt::Horizontal,DiagonalTable::RowRole).toInt();
  49. if (j < firstRow)
  50. continue;
  51.  
  52. sim->setItem(j,i,new QStandardItem("-1.00"));
  53. }
  54. }
  55.  
  56. ui->tableView->resizeColumnsToContents();
  57. }
  58.  
  59. MainWindow::~MainWindow()
  60. {
  61. delete ui;
  62. }
To copy to clipboard, switch view to plain text mode