How to optimize resource usage when drawing image in a QWidget? - c++

I'm subclassing the scrollAreaWidgetContents (which is literally a QWidget) of a QScrollArea and drawing an image on its background.
While resizing the widget, it was using too much CPU (tested with the QScrollArea containing no other widgets) so I tried to find a way to 'optimize' the drawing of the image, and I have written this class:
class ScrollChild : public QWidget
{
Q_OBJECT
public:
QTimer* m_resizeTimer;
int width;
int height;
QPixmap pixmap = QPixmap("...");
QPixmap pixmapScaled;
int pixWidth, prevpixWidth;
int pixHeight, prevpixHeight;
int pixmapPosX;
int pixmapPosY;
// getPos: a dynamic property set in Qt Designer to
// the widget that will be used as base to where the
// pixmap should be drawn.
QWidget* getPos;
QWidget* widget;
ScrollChild(QWidget* parent = 0) : QWidget(parent)
{
initTimer();
};
void resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
width = event->size().width();
height = event->size().height();
m_resizeTimer->start();
}
bool runtime = true;
void showEvent(QShowEvent* event)
{
if (runtime)
{
runtime = false;
// Looking for the property inside showEvent because
// when searched in the constructor its not found.
for (auto wdgt : this->findChildren<QWidget*>())
{
if (!wdgt->property("getPos").isNull())
{
getPos = wdgt;
break;
}
}
}
}
void initTimer()
{
m_resizeTimer = new QTimer(this);
m_resizeTimer->setInterval(100);
m_resizeTimer->setSingleShot(true);
connect(m_resizeTimer, &QTimer::timeout, this, &ScrollChild::resizePicture);
}
void resizePicture()
{
pixWidth = width;
pixHeight = height;
if (pixWidth > 600)
pixWidth = 600;
if (pixHeight > 400)
pixHeight = 400;
if ( (pixWidth != prevpixWidth) && (pixHeight != prevpixHeight) )
pixmapScaled = pixmap.scaled(pixWidth, pixHeight, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
prevpixWidth = pixWidth;
prevpixHeight = pixHeight;
//qDebug() << "w: " << width << " h: " << height;
//qDebug() << "pixmap:\nw: " << pixmap.width() << "h: " << pixmap.height() << "\n";
repaint();
}
void paintEvent(QPaintEvent *e)
{
if (pixmapScaled.isNull())
return;
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
widget = getPos;
pixmapPosX = 0;
pixmapPosY = 0;
// Search the position of the widget marked in
// the Qt Designer.
// As the widget is inside a container we must
// iterate trought her parent adding the x pos
// relative to the scrollAreaWidgetContent.
while (widget != this)
{
pixmapPosX += widget->pos().x();
pixmapPosY += widget->pos().y();
widget = widget->parentWidget();
}
painter.drawPixmap(pixmapPosX, pixmapPosY,
pixmapScaled.width(), pixmapScaled.height(),
pixmapScaled);
}
};
It reduced the CPU usage by 60%~ while keeping the same looking/quality.
Its possible to improve on something else? or write it in a most efficient way?

Related

Looking for the correct Qt5 mechanism to create an identical sized QGridLayout using a custom widget in the grid

So,I'm trying to build a small app that watches my MQTT server for messages and presents a widget with the MQTT JSON content for every message it sees. The logic works well, but I'm having a lot of trouble with adding a QScrollView to contain the grid of widgets. No matter what I do, the eventual widget size is never completely correct.
jsonwidget.h
#pragma once
#include <QtCore/QtCore>
#include <QtWidgets/QtWidgets>
class JsonWidget : public QWidget
{
Q_OBJECT
public:
explicit JsonWidget(QString topic, QWidget *parent = nullptr);
~JsonWidget() override;
void addJson(QJsonDocument &json);
QString topic() { return m_topicString; }
QSize minimumSizeHint() const override;
QSize sizeHint() const override;
protected slots:
void showEvent(QShowEvent *e) override;
void paintEvent(QPaintEvent *e) override;
private:
void populateNewWidget(int localX, QJsonObject obj);
void updateWidget(int localX, int i, QJsonObject obj);
QLabel *m_topic;
QString m_topicString;
uint32_t m_y;
uint32_t m_width;
uint32_t m_origin;
bool m_populated;
};
jsonwidget.cpp
#include "jsonwidget.h"
JsonWidget::JsonWidget(QString topic, QWidget *parent) : QWidget(parent), m_topicString(topic), m_y(0), m_width(0), m_populated(false)
{
m_topic = new QLabel(topic, this);
m_topic->setStyleSheet("QLabel { color: blue; font: 16pt 'Roboto'; }");
m_topic->move(5, m_y + 10);
m_origin = m_topic->height();
m_topic->show();
m_y = m_topic->height() + 10;
QPalette pal = palette();
pal.setColor(QPalette::Window, Qt::white);
setAutoFillBackground(true);
setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
setPalette(pal);
}
JsonWidget::~JsonWidget() = default;
QSize JsonWidget::minimumSizeHint() const
{
if (parentWidget())
return QSize(parentWidget()->width() / 2, m_y);
else
return QSize(20, 20);
}
QSize JsonWidget::sizeHint() const
{
if (parentWidget())
return QSize(parentWidget()->width() / 2, m_y);
else
return QSize(20, 20);
}
void JsonWidget::paintEvent(QPaintEvent* e)
{
QPainter painter(this);
painter.drawRoundedRect(0, 0, width(), height(), 0, 0);
QWidget::paintEvent(e);
}
void JsonWidget::showEvent(QShowEvent *e)
{
QWidget::showEvent(e);
m_topic->adjustSize();
}
void JsonWidget::populateNewWidget(int localX, QJsonObject obj)
{
QFont f("Roboto", 14);
localX += 10;
foreach(const QString& key, obj.keys()) {
QJsonValue value = obj.value(key);
QLabel *label;
switch (value.type()) {
case QJsonValue::Bool:
label = new QLabel(QString("%1 : %2").arg(key).arg(value.toBool()), this);
label->move(localX, m_y);
label->setObjectName(key);
m_y += label->height();
break;
case QJsonValue::Double:
label = new QLabel(QString("%1 : %2").arg(key).arg(value.toDouble()), this);
label->move(localX, m_y);
label->setObjectName(key);
m_y += label->height();
break;
case QJsonValue::String:
label = new QLabel(QString("%1 : %2").arg(key).arg(value.toString()), this);
label->move(localX, m_y);
label->setObjectName(key);
m_y += label->height();
break;
case QJsonValue::Array:
label = new QLabel(QString("%1 : %2").arg(key).arg("NOT DONE YET"), this);
label->move(localX, m_y);
m_y += label->height();
label->setObjectName(key);
break;
case QJsonValue::Object:
label = new QLabel(QString("%1").arg(key), this);
label->move(localX, m_y);
m_y += label->height();
label->setObjectName("object");
populateNewWidget(localX, value.toObject());
break;
case QJsonValue::Undefined:
case QJsonValue::Null:
label = new QLabel(QString("%1 : %2").arg(key).arg("UNDEFINED"), this);
label->move(localX, m_y);
m_y += label->height();
label->setObjectName(key);
break;
}
label->setFont(f);
uint32_t newWidth = label->width() + localX;
if (newWidth > m_width)
m_width = newWidth;
}
}
void JsonWidget::updateWidget(int localX, int i, QJsonObject obj)
{
QList<QLabel*> labels = findChildren<QLabel*>();
bool moveon = false;
int index = i + 1;
localX += 10;
foreach(const QString &key, obj.keys()) {
QJsonValue value = obj.value(key);
for (auto label : labels) {
moveon = false;
switch (value.type()) {
case QJsonValue::Bool:
if (label->objectName() == key) {
label->setText(QString("%1 : %2").arg(key).arg(value.toBool()));
moveon = true;
}
break;
case QJsonValue::Double:
if (label->objectName() == key) {
label->setText(QString("%1 : %2").arg(key).arg(value.toDouble()));
moveon = true;
}
break;
case QJsonValue::String:
if (label->objectName() == key) {
label->setText(QString("%1 : %2").arg(key).arg(value.toString()));
moveon = true;
}
break;
case QJsonValue::Array:
if (label->objectName() == key) {
label->setText("ARRAY UNFINISHED");
moveon = true;
}
break;
case QJsonValue::Object:
updateWidget(localX, index, value.toObject());
moveon = true;
break;
case QJsonValue::Undefined:
case QJsonValue::Null:
if (label->objectName() == key) {
label->setText("UNDEFINED");
moveon = true;
}
break;
}
if (moveon)
break;
}
}
}
void JsonWidget::addJson(QJsonDocument& doc)
{
if (doc.isEmpty() || doc.isNull()) {
qWarning() << __PRETTY_FUNCTION__ << ": Bad JSON document passed in";
return;
}
// qDebug() << __PRETTY_FUNCTION__ << "Topic label height" << m_topic->height();
QJsonObject json = doc.object();
if (m_populated) {
updateWidget(5, 0, json);
}
else {
populateNewWidget(5, json);
m_populated = true;
}
QDateTime now = QDateTime::currentDateTime();
m_topic->setText(QString("Topic: %1 [%2]").arg(m_topicString).arg(now.toString("dd-MM-yyyy h:mm:ss ap")));
}
Note, some of the sizing is just the result of trying things to see what works and what doesn't. The else 20,20 is so I know when parentWidget() isn't valid.
tabwidget.h
#include <QtCore/QtCore>
#include <QtWidgets/QtWidgets>
#include "jsonwidget.h"
/**
* #todo write docs
*/
class TabWidget : public QWidget
{
Q_OBJECT
public:
TabWidget(QWidget *parent = nullptr);
~TabWidget() override;
bool addJson(QString topic, QJsonDocument doc);
private:
bool addNewWidget(int row, int col, QString topic, QJsonDocument doc);
QGridLayout *m_layout;
};
tabwidget.cpp
#include "tabwidget.h"
TabWidget::TabWidget(QWidget *parent) : QWidget(parent)
{
m_layout = new QGridLayout();
setLayout(m_layout);
QPalette pal = palette();
pal.setColor(QPalette::Window, Qt::white);
setAutoFillBackground(true);
setPalette(pal);
}
TabWidget::~TabWidget() = default;
bool TabWidget::addNewWidget(int row, int col, QString topic, QJsonDocument doc)
{
JsonWidget *widget = new JsonWidget(topic);
widget->addJson(doc);
widget->setFixedWidth(parentWidget()->width() / 2);
m_layout->addWidget(widget, row, col);
m_layout->setSizeConstraint(QLayout::SetMinimumSize);
return true;
}
bool TabWidget::addJson(QString topic, QJsonDocument doc)
{
for (int i = 0; i <= m_layout->rowCount(); i++) {
for (int j = 0; j < 2; j++) {
QLayoutItem *item = m_layout->itemAtPosition(i, j);
if (item == nullptr) {
// qDebug() << __PRETTY_FUNCTION__ << "No widget at row" << i << ", column" << j << ", creating a new topic [" << topic << "]";
return addNewWidget(i, j, topic, doc);
}
JsonWidget *jw = static_cast<JsonWidget*>(item->widget());
if (jw->topic() == topic) {
// qDebug() << __PRETTY_FUNCTION__ << "Updating existing [" << topic << "] at row" << i << ", column" << j;
jw->addJson(doc);
return false;
}
}
}
// qDebug() << __PRETTY_FUNCTION__ << "Did not find [" << topic << "] in the widget set";
return false;
}
Finally, because I'll cute the QMainWindow down a bit, I'll put the function I use to create the tab
void MQTTSnoopWindow::newTab(QString topic, QJsonDocument json)
{
QMutexLocker locker(&m_newTabMutex);
QString parentTopic = topic.left(topic.indexOf("/"));
QScrollArea *sa = new QScrollArea(m_mainWidget);
sa->setBackgroundRole(QPalette::Light);
TabWidget *tab = new TabWidget(sa);
tab->addJson(topic, json);
sa->setWidget(tab);
m_mainWidget->addTab(sa, parentTopic);
m_topics++;
// qDebug() << __PRETTY_FUNCTION__ << "Created a new tab";
}
So, my question is, what's the correct way to do this kind of embedding? The goal is two columns of equal sized widgets in a grid. I know I'm doing the sizing wrong, but I'm not sure how. Also, if I had to guess, my parent/child on the objects isn't correct either. So I'm roughly guessing and reading all the layout and widget docs plus google searches for similar questions. This all works great if I don't use the scrollview too. But then I get an ever expanding window which is not what I want.
OK, finally figured it out. Turns out, you have to use layouts across the board, and it works. I was not aware of this. So, with some indirection, my TabWidget and JsonWidget both work correctly, and I needed to embed those in the QScrollArea which was embedded in a layout which is embedded in a widget which is set on the tab.
So, I created a single entity QHBoxLayout, and stick that in an empty parent QWidget as the layout. Then create the QScrollArea, and add that as the only widget in the layout. Finally, create my custom TabWidget and add that as the only element to the QScrollArea.
QWidget *parentWidget = new QWidget(m_mainWidget);
QHBoxLayout *parentLayout = new QHBoxLayout();
parentWidget->setLayout(parentLayout);
QScrollArea *parentScroll = new QScrollArea();
parentLayout->addWidget(parentScroll);
TabWidget *tab = new TabWidget();
parentScroll->setWidgetResizable(true);
parentScroll->setWidget(tab);
tab->addJson(topic, json);
m_mainWidget->addTab(parentWidget, parentTopic);
Then, to get my custom widget back out, you have to unwind the boxes.
QWidget *top = static_cast<QWidget*>(m_mainWidget->widget(i));
QHBoxLayout *topLayout = static_cast<QHBoxLayout*>(top->layout());
QScrollArea *scroller = static_cast<QScrollArea*>(topLayout->itemAt(0)->widget());
TabWidget *widget = static_cast<TabWidget*>(scroller->widget());
I'm going to rename a few entities here now that the layering makes the old names somewhat confusing. I left them to reference the original post. Hoping this helps someone.

Qt C++ Drag QHeaderView between tables

I want to copy the selected column of a QTableWidget to another one.
So I tried to make selected columns draggable by adding this code:
void makeDraggable(QTableWidget *table)
{
table->setDragEnabled(true);
table->setAcceptDrops(true);
table->setSelectionBehavior(QAbstractItemView::SelectColumns);
}
Result I got:
But I want to drag a whole column (horizontal and vertical headers) by clicking on headers only, not on cells, and copy its data to another table including the header text.
Dragging between different tables inside one application can be done with reimplementing custom QHeaderView and QTableWidget. In my example I generate text with indecies of table and column for drag event. Custom header:
#include <QHeaderView>
class ITableManager;
class DraggableHeaderView : public QHeaderView
{
Q_OBJECT
public:
explicit DraggableHeaderView(Qt::Orientation orientation, QWidget *parent = 0);
int tag() const;
void setTag(const int tag);
void setTableManager(ITableManager* manager);
protected:
void mouseMoveEvent(QMouseEvent *e);
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
signals:
public slots:
private:
int m_tag; //internal index of table
ITableManager *m_tableManager; //manager will convert table index into pointer
};
Custom header cpp
#include <QMouseEvent>
#include <QDrag>
#include <QMimeData>
#include <QDebug>
#include <QTableWidget>
#include <ITableManager.h>
DraggableHeaderView::DraggableHeaderView(Qt::Orientation orientation, QWidget *parent) :
QHeaderView(orientation, parent)
{
m_tag = 0;
m_tableManager = 0;
setAcceptDrops(true);
}
void DraggableHeaderView::mouseMoveEvent(QMouseEvent *e)
{
if (e->buttons() & Qt::LeftButton)
{
int index = logicalIndexAt(e->pos());
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
//custom drag text with indecies inside
QString mimeTxt = "MoveHeader;Table:" + QString::number(m_tag) +
";Index:" + QString::number(index);
mimeData->setText(mimeTxt);
drag->setMimeData(mimeData);
Qt::DropAction dropAction = drag->exec();
}
}
int DraggableHeaderView::tag() const
{
return m_tag;
}
void DraggableHeaderView::setTag(const int tag)
{
m_tag = tag;
}
void DraggableHeaderView::dragEnterEvent(QDragEnterEvent *event)
{
if (!m_tableManager)
{
event->ignore();
return;
}
QString dragText = event->mimeData()->text();
int index = dragText.indexOf("MoveHeader;");
if (index == 0)
{
event->accept();
}
else
{
event->ignore();
}
}
void DraggableHeaderView::dropEvent(QDropEvent *event)
{
if (!m_tableManager)
{
event->ignore();
return;
}
QStringList dragText = event->mimeData()->text().split(';');
if (dragText.count() < 3 || dragText.at(0) != "MoveHeader")
{
event->ignore();
return;
}
int tableIndex = dragText.at(1).mid(6).toInt();//6 - length 'Table:'
QTableWidget* tableSrc = m_tableManager->getTableFromIndex(tableIndex);
if (!tableSrc)
{
event->ignore();
return;
}
//dst table as parent for header view
QTableWidget *tableDst = qobject_cast<QTableWidget*> (this->parentWidget());
if (!tableDst)
{
event->ignore();
return;
}
//move column: modify for your needs
//now moves only items text
int columnIndex = logicalIndexAt(event->pos());
int srcColumnIndex = dragText.at(2).mid(6).toInt(); //6 - length of 'Index:'
tableDst->insertColumn(columnIndex);
for (int iRow = 0; iRow < tableDst->rowCount() && iRow < tableSrc->rowCount(); ++iRow)
{
if (tableSrc->item(iRow, srcColumnIndex))
{
tableDst->setItem(iRow, columnIndex,
new QTableWidgetItem(tableSrc->item(iRow, srcColumnIndex)->text()));
}
else
{
tableDst->setItem(iRow, columnIndex, new QTableWidgetItem());
}
}
tableSrc->removeColumn(srcColumnIndex);
}
void DraggableHeaderView::setTableManager(ITableManager *manager)
{
m_tableManager = manager;
}
Now create custom QTableWidget with DraggableHeaderView inside
class CustomTableWidget : public QTableWidget
{
Q_OBJECT
public:
explicit CustomTableWidget(QWidget *parent = 0);
void setTag(const int tag);
void setTableManager(ITableManager* manager);
};
CustomTableWidget::CustomTableWidget(QWidget *parent) :
QTableWidget(parent)
{
DraggableHeaderView *headerView = new DraggableHeaderView(Qt::Horizontal, this);
setHorizontalHeader(headerView);
setAcceptDrops(true);
}
void CustomTableWidget::setTag(const int tag)
{
DraggableHeaderView *header = qobject_cast<DraggableHeaderView*> (horizontalHeader());
if (header)
{
header->setTag(tag);
}
}
void CustomTableWidget::setTableManager(ITableManager *manager)
{
DraggableHeaderView *header = qobject_cast<DraggableHeaderView*> (horizontalHeader());
if (header)
{
header->setTableManager(manager);
}
}
For converting table index to pointer I use ITableManager
class ITableManager
{
public:
virtual QTableWidget* getTableFromIndex(const int index) = 0;
};
And implement it in QMainWindow
class MainWindow : public QMainWindow, ITableManager
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
QTableWidget* getTableFromIndex(const int index);
}
QTableWidget * MainWindow::getTableFromIndex(const int index)
{
switch (index)
{
case 1:
return ui->tableWidget;
case 2:
return ui->tableWidget_2;
default:
return nullptr;
}
}
Dont forget setup tags (indecies) and table manager for tables (in main window constructor)
ui->tableWidget->setTag(1);
ui->tableWidget_2->setTag(2);
ui->tableWidget->setTableManager(this);
ui->tableWidget_2->setTableManager(this);
EDIT: If you want change custom pixmap for dragging just set QDrag::setPixmap
void DraggableHeaderView::mouseMoveEvent(QMouseEvent *e)
{
if (e->buttons() & Qt::LeftButton)
{
int index = logicalIndexAt(e->pos());
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
QString mimeTxt = "MoveHeader;Table:" + QString::number(m_tag) +
";Index:" + QString::number(index);
mimeData->setText(mimeTxt);
drag->setMimeData(mimeData);
drag->setPixmap(pixmapForDrag(index));
Qt::DropAction dropAction = drag->exec();
}
}
And method for taking pixmap of column can be like this
QPixmap DraggableHeaderView::pixmapForDrag(const int columnIndex) const
{
QTableWidget *table = qobject_cast<QTableWidget*> (this->parentWidget());
if (!table)
{
return QPixmap();
}
//image for first 5 row
int height = table->horizontalHeader()->height();
for (int iRow = 0; iRow < 5 && iRow < table->rowCount(); ++iRow)
{
height += table->rowHeight(iRow);
}
//clip maximum size
if (height > 200)
{
height = 200;
}
QRect rect(table->columnViewportPosition(columnIndex) + table->verticalHeader()->width(),
table->rowViewportPosition(0),
table->columnWidth(columnIndex),
height);
QPixmap pixmap(rect.size());
table->render(&pixmap, QPoint(), QRegion(rect));
return pixmap;
}

QDockWidget does not resize properly when interacting with GUI commands of QMainWindow

I have a problem regarding the the non correct resize of the QDockWidget. Specifically when I launch the GUI, the QDockWidget appears like in the image below which is wrong. Also I adjust the size of the QDockWidget during the use of the .ui, however as soon as I interact with the .ui (e.g. using a QPushButton or using a QCheckBox) the QDockWidget gets bigger again:
The expected behavior is the one below, which it does not increase dimension abruptly during the interaction with the .ui but rather remains in position as below:
Below is the most important part of the code I am using for this project and I signed the 3 debugging errors notified by the compiler with // <-- ERROR HERE if that can be useful:
mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
mDockWidget_A = new QDockWidget(QLatin1String("Command Log"));
mDockWidget_A->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
mNewText = new QPlainTextEdit;
mNewText->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
mDockWidget_A->setWidget(mNewText);
mDockWidget_A->installEventFilter(new QDockResizeEventFilter(mNewText,dynamic_cast<QFluidGridLayout*>(mNewText->layout())));
addDockWidget(Qt::BottomDockWidgetArea, mDockWidget_A);
}
qdockresizeeventfilter.h
#include <QObject>
#include <QLayout>
#include <QEvent>
#include <QDockWidget>
#include <QResizeEvent>
#include <QCoreApplication>
#include <QMouseEvent>
#include "qfluidgridlayout.h"
#include "mainwindow.h"
class QDockResizeEventFilter : public QObject
{
public:
friend QMainWindow;
friend QLayoutPrivate;
QDockResizeEventFilter(QWidget* dockChild, QFluidGridLayout* layout, QObject* parent = nullptr)
: QObject(parent), m_dockChild(dockChild), m_layout(layout)
{
}
protected:
bool eventFilter(QObject *p_obj, QEvent *p_event)
{
if (p_event->type() == QEvent::Resize)
{
QResizeEvent* resizeEvent = static_cast<QResizeEvent*>(p_event);
QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(p_obj->parent());
QDockWidget* dock = static_cast<QDockWidget*>(p_obj);
// determine resize direction
if (resizeEvent->oldSize().height() != resizeEvent->size().height())
{
// vertical expansion
QSize fixedSize(m_layout->widthForHeight(m_dockChild->size().height()), m_dockChild->size().height()); // <-- ERROR HERE
if (dock->size().width() != fixedSize.width())
{
m_dockChild->setFixedWidth(fixedSize.width());
dock->setFixedWidth(fixedSize.width());
// cause mainWindow dock layout recalculation
QDockWidget* dummy = new QDockWidget;
mainWindow->addDockWidget(Qt::BottomDockWidgetArea, dummy);
mainWindow->removeDockWidget(dummy);
// adding dock widgets causes the separator move event to end
// restart it by synthesizing a mouse press event
QPoint mousePos = mainWindow->mapFromGlobal(QCursor::pos());
mousePos.setY(dock->rect().bottom());
QCursor::setPos(mainWindow->mapToGlobal(mousePos));
QMouseEvent* grabSeparatorEvent = new QMouseEvent(QMouseEvent::MouseButtonPress,mousePos,Qt::LeftButton,Qt::LeftButton,Qt::NoModifier);
qApp->postEvent(mainWindow, grabSeparatorEvent);
}
}
if (resizeEvent->oldSize().width() != resizeEvent->size().width())
{
// Do nothing
}
}
return false;
}
private:
QWidget* m_dockChild;
QFluidGridLayout* m_layout;
};
#endif // QDockResizeEventFilter_h_
and finally qfluidgridlayout.h
#ifndef QFluidGridLayout_h_
#define QFluidGridLayout_h_
#include <QLayout>
#include <QGridLayout>
#include <QRect>
#include <QStyle>
#include <QWidgetItem>
class QFluidGridLayout : public QLayout
{
public:
enum Direction { downToUp, UpToDown };
QFluidGridLayout(QWidget *parent = nullptr)
: QLayout(parent)
{
setContentsMargins(8,8,8,8);
setSizeConstraint(QLayout::SetMinAndMaxSize);
}
~QFluidGridLayout() {
QLayoutItem *item;
while ((item = takeAt(0)))
delete item;
}
void addItem(QLayoutItem *item) {
itemList.append(item);
}
Qt::Orientations expandingDirections() const {
return nullptr;
}
bool hasHeightForWidth() const {
return false;
}
int heightForWidth(int width) const {
int height = doLayout(QRect(0, 0, width, 0), true, true);
return height;
}
bool hasWidthForHeight() const {
return true;
}
int widthForHeight(int height) const { // <-- ERROR HERE
int width = doLayout(QRect(0, 0, 0, height), true, false);
return width;
}
int count() const {
return itemList.size();
}
QLayoutItem *itemAt(int index) const {
return itemList.value(index);
}
QSize minimumSize() const {
QSize size;
QLayoutItem *item;
foreach (item, itemList)
size = size.expandedTo(item->minimumSize());
size += QSize(2*margin(), 2*margin());
return size;
}
void setGeometry(const QRect &rect) {
QLayout::setGeometry(rect);
doLayout(rect);
}
QSize sizeHint() const {
return minimumSize();
}
QLayoutItem *takeAt(int index) {
if (index >= 0 && index < itemList.size())
return itemList.takeAt(index);
else
return nullptr; }
private:
int doLayout(const QRect &rect, bool testOnly = false, bool width = false) const
{
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom); // <-- ERROR HERE
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
int x = effectiveRect.x();
int y = effectiveRect.y();
int lineHeight = 0;
int lineWidth = 0;
QLayoutItem* item;
foreach(item,itemList)
{
QWidget* widget = item->widget();
if (y + item->sizeHint().height() > effectiveRect.bottom() && lineWidth > 0) {
y = effectiveRect.y();
x += lineWidth + right;
lineWidth = 0;
}
if (!testOnly) {
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
}
y += item->sizeHint().height() + top;
lineHeight = qMax(lineHeight, item->sizeHint().height());
lineWidth = qMax(lineWidth, item->sizeHint().width());
}
if (width) {
return y + lineHeight - rect.y() + bottom;
}
else {
return x + lineWidth - rect.x() + right;
}
}
QList<QLayoutItem *> itemList;
Direction dir;
};
#endif // QFluidGridLayout_h_
I have been reading about this issue frequently here and in this post. However I have been reading about the possibility that this specific object may have some bugs and it was advised to overwrite a resiveEvent. However none of this worked.
I finally, after doing a good amount of research, found this useful post which almost replicates the problem I have and that carries the majority of the two classes above class QFluidGridLayout and class QDockResizeEventFilter.
Although I am using the same approach I am still not able to achieve a normal behavior of this object.
I am also including a snapshot of the debugger:
Can someone explain what am I doing wrong? Thanks so much for shedding light on this issue.
#Emanuele, The post you saw it is mainly for sub-classing the QDockWidget as a child and therefore that solution had to be implemented manually. I think that if you look at this alternative solution you will find it useful.
Try to modify on your constructor adding resizeDocks({dock}, {100}, Qt::Horizontal); as in the post so to have:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
mDockWidget_A = new QDockWidget(QLatin1String("Command Log"));
mDockWidget_A->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
mNewText = new QPlainTextEdit;
mNewText->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
mDockWidget_A->setWidget(mNewText);
mDockWidget_A->installEventFilter(new QDockResizeEventFilter(mNewText,dynamic_cast<QFluidGridLayout*>(mNewText->layout())));
addDockWidget(Qt::BottomDockWidgetArea, mDockWidget_A);
resizeDocks({mDockWidget_A}, {100}, Qt::Horizontal);
}

Qt cannot save pixmap

Hello i have a strange problem to save pixmap.
My Widget Header
public:
QPixmap *base; //Base Poses
QPixmap *Hair; //Hair
QPixmap *Composited; //Final Composition
bool compisition = false;
void Composite();
My Widget Cpp
At paintEvent
base = &pic;
Hair = &hairs;
if(compisition)
{
QPixmap pix(128,192);
QPainter *p = new QPainter(&pix);
p->drawPixmap(0,0,128,192,*base);
p->drawPixmap(0,0,128,192,*Hair);
Composited = &pix;
compisition = false;
}
void AnimPreview::Composite()
{
compisition = true;
this->update();
}
At main form source
void MainWindow::on_commandLinkButton_clicked()
{
QString file = QFileDialog::getSaveFileName(this,
tr("Save Sprite file"),
"",tr("File PNG (*.png)"));
const QPixmap *pix = ui->SpriteFront->pixmap();
if(!file.isEmpty())
{
QFile files(file);
files.open(QIODevice::WriteOnly);
ui->SpriteFront->Composite();
ui->SpriteFront->Composited->save(&files,"PNG");
}
}
When i try to save a file, the process work but whit on error
An unhandled win32 exception
For more information
complete code here
https://pastebin.com/GtaVCXGf
I have avoided reviewing where the error can be generated since there are many possible sources of the problem, among them the following:
It is not necessary that you create QPixmap pointers since in the end you will have the job of eliminating it from memory.
The same happens with QPainter since it only needs to be a local variable, in addition to that the painting is not done immediately, to be sure that it is painted you must call its end() method.
paintEvent is a protected method, so by design we prefer it to remain so.
It is not necessary to use a QFile to save an image, you can directly pass the filename to it.
Considering all the above, we obtain the following:
*.h
#ifndef ANIMPREVIEW_H
#define ANIMPREVIEW_H
#include <QLabel>
#include <QPixmap>
class AnimPreview : public QLabel
{
public:
AnimPreview(QWidget *parent = 0);
void Rotate(int value);
void Composite();
void Create(int _sex, int _hair);
QPixmap Composited;
private:
int sex = 0;
int hair = 0;
bool draw = true;
int rotation = 0;
const int offsetX = 16;
const int offsetY = 32;
QPixmap base;
QPixmap Hair;
bool compisition = false;
protected:
void paintEvent(QPaintEvent *);
};
#endif // ANIMPREVIEW_H
*.cpp
#include "animpreview.h"
#include <QPainter>
AnimPreview::AnimPreview(QWidget *parent):QLabel(parent)
{
}
void AnimPreview::paintEvent(QPaintEvent *){
QPainter p(this);
QPixmap pic;
QPixmap hairs;
if(draw)
{
//Sesso
switch(sex)
{
case 0:
pic.load(":/Male/Base/Res/man_f.png");
break;
case 1:
pic.load(":/Male/Base/Res/woman_f.png");
break;
}
//capelli
if(sex == 1)
{
switch(hair)
{
case 1:
hairs.load(":/Female/Hair/Res/7_aqua.png");
break;
case 2:
hairs.load(":/Female/Hair/Res/5_gold.png");
break;
}
}
if(sex == 0)
{
switch (hair)
{
case 0:
break;
case 1:
hairs.load(":/Male/Hair/Res/1_aqua.png");
break;
case 2:
hairs.load(":/Male/Hair/Res/14_black.png");
break;
}
}
}
p.drawPixmap(width()/2 - offsetX,height()/2 - offsetY,pic,0,rotation,32,48);
p.drawPixmap(width()/2 - offsetX,height()/2 - offsetY,hairs,0,rotation,32,48);
p.drawRect(0,0, width()-1, height()-1);
base = pic;
Hair = hairs;
if(compisition)
{
QPixmap pix(128,192);
QPainter p(&pix);
p.drawPixmap(0,0,128,192, base);
p.drawPixmap(0,0,128,192, Hair);
p.end();
Composited = pix;
compisition = false;
}
}
void AnimPreview::Rotate(int value)
{
rotation = value;
update();
}
void AnimPreview::Create(int _sex, int _hair)
{
sex = _sex;
hair = _hair;
draw = true;
}
void AnimPreview::Composite()
{
compisition = true;
update();
}
void MainWindow::on_commandLinkButton_clicked()
{
QString file = QFileDialog::getSaveFileName(this,
tr("Save Sprite file"),
"",tr("File PNG (*.png)"));
if(!file.isEmpty())
{
ui->SpriteFront->Composite();
ui->SpriteFront->Composited.save(file,"PNG");
}
}

Set up a QGridLayout with equal cell size with QWidgets + QPainter.DrawImage not displaying the image

First I will describe what I want to do. I am using QT to build a window application. What I essentially wanted to do is say, I want a gridlayout which will be 10x10 and all the cells will be 5x5 but without having to populate the grid first!
I populate the grid with my Widget class which extends the QWidget class. the Widget or QWidget has on the constructor a .resize(w,h) so i though this would restrain the w and h of the widget when loaded. However when i add a widget to the Grid it just goes in the center and the next where ever it wants to!
Furthermore inside the QWidget::Widget when its been clicked and blClicked = true then an image should be loaded.. but its not! *see code for details.
What I essentially want to achieve is this but with having 0 qwidgets in the qgridlayout and increasing them 1 by 1 sorting from bottom left to top left etc.
Thanks!
Code following:
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QPainter>
#include "widget.h"
#include <iostream>
class Widget : public QWidget
{
private:
Q_OBJECT
int x,y,w,h;
bool blClicked;
QImage img;
public:
explicit Widget(QWidget *parent = 0);
Widget(int x,int y,int w);
void paintEvent(QPaintEvent* event);
void mousePressEvent (QMouseEvent* event );
int getX();
int getY();
QWidget* toQWidget();
};
#endif // WIDGET_H
widget.cpp
Widget::Widget(int x, int y,int w)
{
this->x = x;
this->y = y;
this->w = w;
this->h = w;
this->resize(this->w,this->h);
blClicked = false;
img.load("car.png");
update();
}
void Widget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setWindow(x,y,w,h);
painter.drawRect(x,y,w,h);
if(blClicked)
{
std::cout << "add image" << std::endl;
painter.drawImage(x,y,img);
}
}
void Widget::mousePressEvent(QMouseEvent *event)
{
std::cout << "mouse click pressed" << std::endl;
blClicked = !blClicked;
update();
}
mainwindow.cpp
void MainWindow::on_btnCreateMap_clicked()
{
using namespace std;
if(ui->lnHeight->text() != NULL && ui->lnWidth->text() != NULL)
{
std::cout << "click" << std::endl;
try {
width = stoi(ui->lnWidth->text().toStdString());
height = stoi(ui->lnHeight->text().toStdString());
//Mapper m(height,width);
l.push_back(new Widget(2,2,10));
ui->gridWidget->addWidget(l[l.size()-1]);
for (int i = 0; i < l.size(); i++)
{
std::cout << "list size" << l.size() << std::endl;
std::cout << "address" << l[i] << std::endl;
}
}
catch (exception& e)
{
cout << "Standard exception: " << e.what() << endl;
}
}
}