Sort QTableView column containing index widgets - c++

I have a QTableView whose model is a QStandardItemModel. In one of the columns, I create new indexes and use setIndexWidget to assign them to a custom color-selection widget. I'd like to be able to sort the view using this column so users could group items with like colors. I've implemented the operator< for the custom widget, but whenever I try to use it I get a run-time debug assertion saying:
Expression: Invalid operator<
In order to sort by this column do I need to set some role data for the QStandardItem the widgets are attached to?
I think the operator< code is fine but I'm including it as well just in case.
bool operator<(const QPenWidget &rhs) const
{
// use the RGB value to index the color
int r_lhs, g_lhs, b_lhs, rgb_lhs;
int r_rhs, g_rhs, b_rhs, rgb_rhs;
this->m_pen.color().getRgb(&r_lhs, &g_lhs, &b_lhs);
rgb_lhs = (r_lhs * 65536) + (g_lhs * 256) + b_lhs;
rhs.m_pen.color().getRgb(&r_rhs, &g_rhs, &b_rhs);
rgb_rhs = (r_rhs * 65536) + (g_rhs * 256) + b_rhs;
if (rgb_lhs != rgb_rhs)
return rgb_lhs < rgb_rhs;
if (m_pen.style() != rhs.m_pen.style())
return m_pen.style() < rhs.m_pen.style();
return m_pen.width() < rhs.m_pen.width();
}

By default, the QStandardItemModel uses whatever the data for the Qt::DisplayRole is to do the sorting. While it's possible to change the role using setSortRole, for index widgets the easiest thing to do is put some sorting hash value into the display role (note: this doesn't affect how the widget is shown in the view).
For this application here is a hash function:
int sortKey()
{
int r, g, b, rgb;
m_pen.color().getRgb(&r, &g, &b);
rgb = (r * 65536) + (g * 256) + b;
rgb *= styleModel->rowCount();
rgb += m_pen.style();
rgb *= widthModel->rowCount();
rgb += m_pen.width();
return rgb;
}
Then when creating the index:
QStandardItem* penItem = new QStandardItem;
QPenWidget* pen = new QPenWidget;
tableView->setIndexWidget(penIndex, pen);
penItem->setData(pen->sortKey(), Qt::DisplayRole);

Related

How to create a custom winrt::Microsoft::AI::MachineLearning::TensorFloat16Bit?

How do I create a TensorFloat16Bit when manually doing a tensorization of the data?
We tensorized our data based on this Microsoft example, where we are converting 255-0 to 1-0, and changing the RGBA order.
...
std::vector<int64_t> shape = { 1, channels, height , width };
float* pCPUTensor;
uint32_t uCapacity;
// The channels of image stored in buffer is in order of BGRA-BGRA-BGRA-BGRA.
// Then we transform it to the order of BBBBB....GGGGG....RRRR....AAAA(dropped)
TensorFloat tf = TensorFloat::Create(shape);
com_ptr<ITensorNative> itn = tf.as<ITensorNative>();
CHECK_HRESULT(itn->GetBuffer(reinterpret_cast<BYTE**>(&pCPUTensor), &uCapacity));
// 2. Transform the data in buffer to a vector of float
if (BitmapPixelFormat::Bgra8 == pixelFormat)
{
for (UINT32 i = 0; i < size; i += 4)
{
// suppose the model expects BGR image.
// index 0 is B, 1 is G, 2 is R, 3 is alpha(dropped).
UINT32 pixelInd = i / 4;
pCPUTensor[pixelInd] = (float)pData[i];
pCPUTensor[(height * width) + pixelInd] = (float)pData[i + 1];
pCPUTensor[(height * width * 2) + pixelInd] = (float)pData[i + 2];
}
}
ref: https://github.com/microsoft/Windows-Machine-Learning/blob/2179a1dd5af24dff4cc2ec0fc4232b9bd3722721/Samples/CustomTensorization/CustomTensorization/TensorConvertor.cpp#L59-L77
I just converted our .onnx model to float16 to verify if that would provide some performance improvements on the inference when the available hardware provides support for float16. However, the binding is failing and the suggestion here is to pass a TensorFloat16Bit.
So if I swap the TensorFloat for TensorFloat16Bit I get an access violation exception at pCPUTensor[(height * width * 2) + pixelInd] = (float)pData[i + 2]; because pCPUTensor is half of the size of what it was. It seems like I should be reinterpreting_cast to uint16_t** or something among those lines, so pCPUTensor will have the same size as when it was a TensorFloat, but then I get further errors that it can only be uint8_t** or BYTE**.
Any ideas on how I can modify this code so I can get a custom TensorFloat16Bit?
Try the factory methods on TensorFloat16Bit.
However, you will need to convert you data to float16:
https://stackoverflow.com/a/60047308/11998382
Also, I might recommend you instead do the conversion within the onnx model.

QTableView issue with resizeEvent()

I have an object that inherits QTableView and overrides the resizeEvent() method to set the widths of the table columns to a percantage of the available space if the table as a whole is resized.
Code is as follows:
void DDUTableView::resizeEvent(QResizeEvent* ev)
{
int num_columns = NUM_ELEMENTS(COLUMN_WIDTHS);
if (num_columns > 0) {
int width = ev->size().width();
int used_width = 0;
// Set our widths to be a percentage of the available width
for (int i = 0; i < num_columns - 1; i++) {
int column_width = (width * COLUMN_WIDTHS[i]) / 100;
this->setColumnWidth(i, column_width);
used_width += column_width;
}
// Set our last column to the remaining width
this->setColumnWidth(num_columns - 1, width - used_width);
}
// Call our base resizeEvent to handle the vertical resizing which
// we don't bother with here
QTableView::resizeEvent(ev);
}
This all works fine until the user manually resizes one of the columns and stretches it beyond the viewport (bringing up the horizontal scrollbar). This then triggers my resizeEvent() call which resets the column widths to the percentage defaults.
I can overcome this issue by connecting to the sectionResized() signal on the table header and setting a timer running. If the resizeEvent() is called whilst the timer is active then I don't recalculate the table widths.
Code below:
connect(horizontalHeader(), SIGNAL(sectionResized(int, int, int)), this, SLOT(slotSectionResized(int, int, int)));
void DDUTableView::slotSectionResized(int /*logicalIndex*/, int /*oldSize*/, int /*newSize*/)
{
timer_->start(500);
}
void DDUTableView::resizeEvent(QResizeEvent* ev)
{
if (timer_->isActive()) {
return;
}
// etc
This works but is messy. Anyway the simple question is can I prevent the resizeEvent() from being called if the user manually adjusts the column headers beyond the scope of the viewport? Alternatively, if not, is it possible to identify in the resizeEvent() whether this particular situation has occurred without having to set timers and the like?
Checking if the scrollbar is visible worked for my scenario.
void DDUTableView::resizeEvent(QResizeEvent* ev)
{
if (!horizontalScrollBar()->isVisible()) {
int num_columns = NUM_ELEMENTS(COLUMN_WIDTHS);
if (num_columns > 0) {
int width = ev->size().width();
int used_width = 0;
// Set our widths to be a percentage of the available width
for (int i = 0; i < num_columns - 1; i++) {
int column_width = (width * COLUMN_WIDTHS[i]) / 100;
this->setColumnWidth(i, column_width);
used_width += column_width;
}
// Set our last column to the remaining width
this->setColumnWidth(num_columns - 1, width - used_width);
}
}
// Call our base resizeEvent to handle the vertical resizing which
// we don't bother with here
QTableView::resizeEvent(ev);
}

QTableView resize vertically data not refreshed

I've implemented a table through deriving QTableView and QAbstractTableModel. It all seems to work fine except when I resize the table vertically the rows that were originally out of view don't show any data.
There is no issue when resizing horizontally possibly because I've overridden the resizeEvent() method and am recalculating column widths which I obviously don't do if the table is resized vertically.
I'm using the following code in the model to add data to the table:
bool DDUTableModel::insertRow(int row, const QModelIndex& parent)
{
beginInsertRows(parent, row, row);
digital_display_list_.append(DigitalDisplayData(path_));
endInsertRows();
return true;
}
The resizeEvent() looks like this:
void DDUTableView::resizeEvent(QResizeEvent* ev)
{
int num_columns = NUM_ELEMENTS(COLUMN_WIDTHS);
if (num_columns > 0) {
int width = ev->size().width();
int used_width = 0;
// Set our widths to be a percentage of the available width
for (int i = 0; i < num_columns - 1; i++) {
int column_width = (width * COLUMN_WIDTHS[i]) / 100;
this->setColumnWidth(i, column_width);
used_width += column_width;
}
// Set our last column to the remaining width
this->setColumnWidth(num_columns - 1, width - used_width);
}
}
Any ideas?
The problem was with the resizeEvent(). I need to also invoke the method in the QTableView class that I derived from to force a refresh on vertical resizing. Amended method looks like this:
void DDUTableView::resizeEvent(QResizeEvent* ev)
{
int num_columns = NUM_ELEMENTS(COLUMN_WIDTHS);
if (num_columns > 0) {
int width = ev->size().width();
int used_width = 0;
// Set our widths to be a percentage of the available width
for (int i = 0; i < num_columns - 1; i++) {
int column_width = (width * COLUMN_WIDTHS[i]) / 100;
this->setColumnWidth(i, column_width);
used_width += column_width;
}
// Set our last column to the remaining width
this->setColumnWidth(num_columns - 1, width - used_width);
}
QTableView::resizeEvent(ev);
}

QTableView slow performance with 1000s of visible cells

I'm using QTableView in Qt 4.8.4 to visualize a lot of data (large/many protein amino acid sequences) and I'd like to be able to make cells as small as possible so I can pack as many as possible into a given window. The problem I'm running into is that when there are many cells displayed at once, everything (e.g. scrolling, resizing, and in general repainting) slows down to a crawl. Here's some sample code (adapted from the examples/tutorials/1_readonly tutorial):
MyModel::MyModel(QObject *parent):QAbstractTableModel(parent){}
int MyModel::rowCount(const QModelIndex & /*parent*/) const {
return 200;
}
int MyModel::columnCount(const QModelIndex & /*parent*/) const {
return 60;
}
QVariant MyModel::data(const QModelIndex &index, int role) const {
if (role == Qt::DisplayRole){
return QString("%1").arg(index.row()%10);
}
return QVariant();
}
and here's the code which runs the table view:
int main(int argc, char *argv[]){
QApplication a(argc, argv);
QTableView tableView;
tableView.horizontalHeader()->setDefaultSectionSize(15);
tableView.verticalHeader()->setDefaultSectionSize(15);
tableView.setFont(QFont("Courier",12));
MyModel myModel(0);
tableView.setModel( &myModel );
tableView.setGeometry(0,0,1000,1000);
tableView.show();
return a.exec();
}
When I use Instruments on OSX while scrolling up and down, it's spending a lot of time in QWidgetPrivate::drawWidget and down the stack, QWidgetPrivate::paintSiblingsRecursive... i.e., it's spending a lot of time redrawing my table.
I'm new to Qt, so I'm not sure how to approach this problem. Should I:
override the paint method? i.e. perhaps I could save my whole table as an image, and when scrolling happens, to just repaint the image until movement stops (and then return to painting the table directly)?
Not use tables in Qt at all? Perhaps I can just use a Text field to accomplish my purposes? e.g. for each letter in the text, i'd like hovertext, selections, coloring letter's backgrounds, etc.
Both of these options seem like a lot of work to make up for ground lost by switching away from QTableView. Are there any other suggestions?
QTableView is known to be slow when dealing with large datasets. I suggest you to switch to Qt Graphics View Framework. It's much more efficient and is flexible enough to display a table.
QGraphicsScene scene;
QFont font("Courier",12);
QFontMetrics font_metrics(font);
int padding = 2;
int column_width = font_metrics.width("X") + padding * 2;
int row_height = font_metrics.height() + padding * 2;
int rows = 200, columns = 60;
for(int x = 0; x < columns; x++) {
for(int y = 0; y < rows; y++) {
QGraphicsSimpleTextItem* item = scene.addSimpleText(QString().setNum(y % 10), font);
item->setPos(x * column_width + padding, y * row_height + padding);
}
}
for(int x = 0; x < columns + 1; x++) {
int line_x = x * column_width;
scene.addLine(line_x, 0, line_x, rows * row_height)->setPen(QPen(Qt::gray));
}
for(int y = 0; y < rows + 1; y++) {
int line_y = y * row_height;
scene.addLine(0, line_y, columns * column_width, line_y)->setPen(QPen(Qt::gray));
}
QGraphicsView view(&scene);
view.resize(700, 700);
view.show();
Try to use QTreeView, but set uniformRowHeights to true. Millions of items worked last time I've checked.
EDIT: QTreeView supports tables and more!
i'm using a 1e8 rows table and had to switch to QTreeView with setUniformRowHeights(true);

Qt setColumnWidth does not work

Have written the following code:
m_selectCategoryTableWidget = new QTableWidget;
m_selectCategoryTableWidget->setRowCount(0);
m_selectCategoryTableWidget->setColumnCount(2);
m_selectCategoryTableWidget->setHorizontalHeaderLabels(QStringList()<<tr("Category")<<tr("Number of items"));
m_selectCategoryTableWidget->verticalHeader()->setVisible(false);
m_selectCategoryTableWidget->horizontalHeader()->setStretchLastSection(true);
//m_selectCategoryTableWidget->setColumnWidth(0,400);
m_selectCategoryTableWidget->resizeColumnsToContents();
m_selectCategoryTableWidget->setColumnWidth(1,100); //this does not take effect
Please help.
Well, Qt's logic is so, that after column resize, scroll bar area checks how columns fit into it. And if the sum of all columns' widths is less than the widget's visible width, then the last column gets resized to fill up the space leading to no visible result of calling setColumnWidth(). Actually two resizes happen - to shrink and reverse to enlarge.
So, the lesson is - get control's visible width, recalculate sizes as you want, and resize all but the last column. For two column case it's really simple:
int secondColumnWidth = 100;
int firstColumnWidth = m_selectCategoryTableWidget->width() - secondColumnWidth;
if (firstColumnWidth > 0)
{
m_selectCategoryTableWidget->setColumnWidth(0, firstColumnWidth);
}
else
{
m_selectCategoryTableWidget->resizeColumnsToContents();
}
Good luck!
It is also possible to specify that you want the first column to fill the remaining space instead of the last column. Unfortunately this does seem to prevent the user from being able to manually resize the columns.
int secondColumnWidth = 100;
m_selectCategoryTableWidget->header()->setStretchLastSection(false);
m_selectCategoryTableWidget->header()->setResizeMode(0, QHeaderView::Stretch);
m_selectCategoryTableWidget->setColumnWidth(1, secondColumnWidth);
This will automatically resize the columns to fit ("view" is an QTableView* and model is a QSqlQueryModel*).
static_cast<QTableView*>(view)->horizontalHeader()
->resizeSections(QHeaderView::ResizeToContents);
QFontMetrics fm(view->font());
for (int i = 0 ; i < model->record().count(); ++i)
{
int maxLength = 0;
for (int j = 0; j < model->rowCount(); ++j)
{
QString cell = model->record(j).value(i).toString();
if (fm.width(cell) > maxLength)
{
maxLength = fm.width(cell);
}
}
QHeaderView& hv = *static_cast<QTableView*>(view)->horizontalHeader();
if (maxLength > hv.sectionSize(i))
{
hv.resizeSection(i, maxLength * 1.5);
}
}