What is the easiest way (without subclassing QTableWidget if possible) to put a personnal Widget (which is a group of QPixmap + QLabel + QComboBox in my case) as headers in a QTableWidget ?
It is relatively easy to do it for the array content as we have the function QTableWidget::setCellWidget(int row, int column, QWidget * widget), but for the headers, we only have QTableWidget::setHorizontalHeaderItem (int column, QTableWidgetItem * item)
Thank you very much !
EDIT :
Maybe I've found a very unelegant way to do this that may work :
1) I initialize each column with an empty QString header
2) I extract the size and position of each section of the header with _table->horizontalHeader()->sectionPosition(index) and _table->horizontalHeader()->sectionSize(index)
3) Then I draw a QFrame onto each section with a little margin
4) And finally, I load my QWidgets in these QFrame
If any one else has a better solution, I'm interested...
I guess setCellWidget() will do the job. I don't see an easier way. Instead of a QFrame you might also use a QWidget, if you don't use the additional functionality of QFrame.
I researched a lot to create an excel like filter header.
Replacing header does not have a simple nor fast way.
Finaly I found this blog post: Qt Support Weekly #27 – Widgets on a header. Based on its guide, here is complete code to create a QHeaderView and QTableWidget subclass that can accept custom widget as header item:
customtableheader.h:
#ifndef CUSTOMTABLEHEADER_H
#define CUSTOMTABLEHEADER_H
#include <QWidget>
#include <QHeaderView>
class CustomTableHeader : public QHeaderView
{
public:
struct Margins
{
int left;
int right;
int top;
int buttom;
Margins(int left = 2, int right = 2, int top = 2, int buttom = 2);
};
CustomTableHeader(Qt::Orientation orientation,
QWidget *parent = nullptr);
void FixComboPositions();
void SetItemWidget(int index, QWidget * widget);
void SetItemMargins(int index, Margins margins);
private:
struct Item
{
QWidget * item;
Margins margins;
Item();
};
QMap<int, Item> mItems;
void showEvent(QShowEvent * e);
void HandleSectionResized(int i);
void HandleSectionMoved(int logical, int oldVisualIndex, int newVisualIndex);
};
#endif // CUSTOMTABLEHEADER_H
customtableheader.cpp:
#include "customtableheader.h"
CustomTableHeader::CustomTableHeader(Qt::Orientation orientation, QWidget * parent) :
QHeaderView(orientation, parent)
{
connect(this, &CustomTableHeader::sectionResized, this, &CustomTableHeader::HandleSectionResized);
connect(this, &CustomTableHeader::sectionMoved, this, &CustomTableHeader::HandleSectionMoved);
}
void CustomTableHeader::showEvent(QShowEvent *e)
{
for (int i = 0; i < count(); i++)
{
if (!mItems[i].item)
mItems[i].item = new QWidget(this);
else
mItems[i].item->setParent(this);
mItems[i].item->setGeometry(sectionViewportPosition(i) + mItems[i].margins.left,
mItems[i].margins.top,
sectionSize(i) - mItems[i].margins.left - mItems[i].margins.right - 1,
height() - mItems[i].margins.top - mItems[i].margins.buttom - 1);
mItems[i].item->show();
}
QHeaderView::showEvent(e);
}
void CustomTableHeader::HandleSectionResized(int i)
{
int logical;
for (int j = visualIndex(i); j < count(); j++)
{
logical = logicalIndex(j);
mItems[logical].item->setGeometry(sectionViewportPosition(logical) + mItems[i].margins.left,
mItems[i].margins.top,
sectionSize(logical) - mItems[i].margins.left - mItems[i].margins.right - 1,
height() - mItems[i].margins.top - mItems[i].margins.buttom - 1);
}
}
void CustomTableHeader::HandleSectionMoved(int logical, int oldVisualIndex, int newVisualIndex)
{
Q_UNUSED(logical);
for (int i = qMin(oldVisualIndex, newVisualIndex); i < count(); i++)
{
int logical = logicalIndex(i);
mItems[logical].item->setGeometry(sectionViewportPosition(logical) + mItems[i].margins.left,
mItems[i].margins.top,
sectionSize(logical) - mItems[i].margins.left - mItems[i].margins.right - 1,
height() - mItems[i].margins.top - mItems[i].margins.buttom - 1);
}
}
void CustomTableHeader::FixComboPositions()
{
for (int i = 0; i < count(); i++)
mItems[i].item->setGeometry(sectionViewportPosition(i) + mItems[i].margins.left,
mItems[i].margins.top,
sectionSize(i) - mItems[i].margins.left - mItems[i].margins.right - 1,
height() - mItems[i].margins.top - mItems[i].margins.buttom - 1);
}
void CustomTableHeader::SetItemWidget(int index, QWidget * widget)
{
widget->setParent(this);
mItems[index].item = widget;
}
void CustomTableHeader::SetItemMargins(int index, CustomTableHeader::Margins margins)
{
mItems[index].margins = margins;
}
CustomTableHeader::Margins::Margins(int left, int right, int top, int buttom) :
left(left),
right(right),
top(top),
buttom(buttom)
{
}
CustomTableHeader::Item::Item() :
item(nullptr)
{
}
customheaderedtable.h:
#ifndef CUSTOMHEADEREDTABLE_H
#define CUSTOMHEADEREDTABLE_H
#include <QtCore/qglobal.h>
#include <QTableWidget>
#include "customtableheader.h"
class CustomHeaderedTable : public QTableWidget
{
public:
CustomHeaderedTable(QWidget * parent = Q_NULLPTR);
void scrollContentsBy(int dx, int dy);
void SetHorizontalHeaderItemWidget(int column, QWidget * widget);
void SetHorizontalHeaderItemMargins(int column, CustomTableHeader::Margins margins);
private:
CustomTableHeader * mHorizontalHeader;
};
#endif // CUSTOMHEADEREDTABLE_H
customheaderedtable.cpp:
#include "customheaderedtable.h"
CustomHeaderedTable::CustomHeaderedTable(QWidget * parent) :
QTableWidget(parent)
{
mHorizontalHeader = new CustomTableHeader(Qt::Orientation::Horizontal, this);
setHorizontalHeader(mHorizontalHeader);
}
void CustomHeaderedTable::scrollContentsBy(int dx, int dy)
{
QTableWidget::scrollContentsBy(dx, dy);
if (dx != 0)
mHorizontalHeader->FixComboPositions();
}
void CustomHeaderedTable::SetHorizontalHeaderItemWidget(int column, QWidget * widget)
{
mHorizontalHeader->SetItemWidget(column, widget);
}
void CustomHeaderedTable::SetHorizontalHeaderItemMargins(int column, CustomTableHeader::Margins margins)
{
mHorizontalHeader->SetItemMargins(column, margins);
}
Sample useage:
QComboBox * combo = new QComboBox();
combo->addItem("1");
combo->addItem("2");
combo->addItem("3");
CustomHeaderedTable table = new CustomHeaderedTable(this);
table->setRowCount(70);
table->setColumnCount(70);
table->SetHorizontalHeaderItemWidget(1, combo);
Related
I'm following this series on youtube Node Editor. I'm attempting to learn both Qt and C++ at the same time, possibly a stupid idea, but "hello world" tutorials don't do much for me.
Episode 1 We are to create a grid using the drawBackground method, however before this in the class constructor we have set the background to a light grey colour. When I run the code without the drawBackground the scene shows the light grey background. However when I run it with the drawBackground method uncommented the grid is drawn but the background is now white.
Why does the drawBackground() method override the QDMGraphicsScene() setBackgroundBrush?
qdmgraphicsscene.h
#ifndef QDMGRAPHICSSCENE_H
#define QDMGRAPHICSSCENE_H
#include <QGraphicsScene>
#include <QtGui>
#include <QtCore>
#include "math.h"
class QDMGraphicsScene : public QGraphicsScene
{
public:
QDMGraphicsScene();
//Settings
QColor _colour_background, _colour_light, _colour_dark;
QPen _pen_light, _pen_dark;
int scene_width, scene_height, gridSize, gridSquares;
QVector<QLine> lines_light, lines_dark;
void drawBackground(QPainter *painter, const QRectF &rect);
};
#endif // QDMGRAPHICSSCENE_H
qdmgraphicsscene.cpp
#include "qdmgraphicsscene.h"
QDMGraphicsScene::QDMGraphicsScene()
{
this->gridSize = 60;
this->gridSquares = 3;
this->_colour_background = QColor(57,57,57);
this->_colour_light = QColor(47,47,47);
this->_colour_dark = QColor(41, 41, 41);
this->_pen_light = QPen(this->_colour_light);
this->_pen_light.setWidth(1);
this->_pen_dark = QPen(this->_colour_dark);
this->_pen_dark.setWidth(2);
this->scene_width = 64000;
this->scene_height = 64000;
this->setSceneRect(-this->scene_width/2, -this->scene_height/2, this->scene_width, this->scene_height);
this->setBackgroundBrush(this->_colour_background);
}
void QDMGraphicsScene::drawBackground(QPainter *painter, const QRectF &rect)
{
//create grid
int left = (int)floor(rect.left());
int right = (int)ceil(rect.right());
int top = (int)floor(rect.top());
int bottom = (int)ceil(rect.bottom());
int first_left = left - (left % this->gridSize);
int first_top = top - (top % this->gridSize);
//compute lines
for(int x = first_left; x < right; x=x+this->gridSize){
if(x % (this->gridSize * this->gridSquares) != 0){
this->lines_light.append(QLine(x,top,x,bottom));
} else {
this->lines_dark.append(QLine(x,top,x,bottom));
}
}
for(int y = first_top; y < bottom; y=y+this->gridSize){
if(y % (this->gridSize * this->gridSquares) != 0){
this->lines_light.append(QLine(left,y,right,y));
} else {
this->lines_dark.append(QLine(left,y,right,y));
}
}
//draw lines
painter->setPen(this->_pen_light);
painter->drawLines(this->lines_light);
painter->setPen(this->_pen_dark);
painter->drawLines(this->lines_dark);
}
I suspect the problem is simply that your drawBackground override doesn't use the background brush -- you just set the QPainter pen and use it to draw lines.
I think the simplest solution would be to call the base class implementation of drawBackground at the top of your own, so...
void QDMGraphicsScene::drawBackground (QPainter *painter, const QRectF &rect)
{
/*
* Call base class drawBackground implementation to draw background.
*/
QGraphicsScene::drawBackground(painter, rect);
//create grid
.
.
.
I have a layout that looks like this.
Where:
Blue: rectangle it's a ScrollArea
Orange: rectangles are the widgets from that ScrollArea
My code:
#include <QtWidgets>
///////////////////////////////////////////////////////////////////////////////////////
class RoundedPolygon : public QPolygon {
public:
RoundedPolygon() { SetRadius(10); }
void SetRadius(unsigned int iRadius) { m_iRadius = iRadius; }
const QPainterPath &GetPath() {
m_path = QPainterPath();
if (count() < 3) {
qDebug() << "!! Polygon should have at least 3 points !!";
return m_path;
}
QPointF pt1;
QPointF pt2;
for (int i = 0; i < count(); i++) {
pt1 = GetLineStart(i);
if (i == 0)
m_path.moveTo(pt1);
else
m_path.quadTo(at(i), pt1);
pt2 = GetLineEnd(i);
m_path.lineTo(pt2);
}
// close the last corner
pt1 = GetLineStart(0);
m_path.quadTo(at(0), pt1);
return m_path;
}
private:
QPointF GetLineStart(int i) const {
QPointF pt;
QPoint pt1 = at(i);
QPoint pt2 = at((i + 1) % count());
float fRat = m_iRadius / GetDistance(pt1, pt2);
if (fRat > 0.5f)
fRat = 0.5f;
pt.setX((1.0f - fRat) * pt1.x() + fRat * pt2.x());
pt.setY((1.0f - fRat) * pt1.y() + fRat * pt2.y());
return pt;
}
QPointF GetLineEnd(int i) const {
QPointF pt;
QPoint pt1 = at(i);
QPoint pt2 = at((i + 1) % count());
float fRat = m_iRadius / GetDistance(pt1, pt2);
if (fRat > 0.5f)
fRat = 0.5f;
pt.setX(fRat * pt1.x() + (1.0f - fRat) * pt2.x());
pt.setY(fRat * pt1.y() + (1.0f - fRat) * pt2.y());
return pt;
}
float GetDistance(QPoint pt1, QPoint pt2) const {
int fD = (pt1.x() - pt2.x()) * (pt1.x() - pt2.x()) + (pt1.y() - pt2.y()) * (pt1.y() - pt2.y());
return sqrtf(fD);
}
private:
QPainterPath m_path;
unsigned int m_iRadius{};
};
class PolygonButtonWidget : public QWidget {
Q_OBJECT
public:
explicit PolygonButtonWidget(QWidget *parent = nullptr) : QWidget(parent) {}
~PolygonButtonWidget() override = default;
protected:
void resizeEvent(QResizeEvent *event) override {
float ratioW = 8;
float ratioH = 3;
// ui->scrollAreaWidgetContents->setFixedSize(5000, h);
float thisAspectRatio = (float) event->size().width() / event->size().height();
if (thisAspectRatio < ratioW / ratioH) {
float w = event->size().height() * ratioW / ratioH;
float h = event->size().height();
qDebug() << hasHeightForWidth() << " " << w << " " << h;
this->resize(w, h);
if (m_nrButtons != 0) {
this->move((w + 20) * m_nrButtons, this->y());
}
}
QWidget::resizeEvent(event);
}
int m_nrButtons{};
public:
void setMNrButtons(int mNrButtons) {
m_nrButtons = mNrButtons;
}
protected:
void paintEvent(QPaintEvent *event) override {
int offset = 50;
m_polygon.clear();
m_polygon.emplace_back(0, height()); //DOWN-LEFT
m_polygon.emplace_back(width() - offset, height()); //DOWN-RIGHT
m_polygon.emplace_back(width(), 0); //TOP-RIGHT
m_polygon.emplace_back(0 + offset, 0);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
RoundedPolygon poly;
poly.SetRadius(15);
for (QPoint point: m_polygon) {
poly << point;
}
QBrush fillBrush;
fillBrush.setColor(Qt::darkBlue);
fillBrush.setStyle(Qt::SolidPattern);
QPainterPath path;
path.addPath(poly.GetPath());
painter.fillPath(path, fillBrush);
}
void mousePressEvent(QMouseEvent *event) override {
auto cursorPos = mapFromGlobal(QCursor::pos());
qDebug() << "X: " << cursorPos.x() << " Y: " << cursorPos.y();
inside(cursorPos, m_polygon);
qDebug() << "Pressed";
}
private:
std::vector<QPoint> m_polygon;
bool inside(QPoint point, std::vector<QPoint> polygon) {
auto x = point.x();
auto y = point.y();
auto inside = false;
auto i = 0;
auto j = polygon.size() - 1;
while (i < polygon.size()) {
auto xi = polygon[i].x();
auto yi = polygon[i].y();
auto xj = polygon[j].x();
auto yj = polygon[j].y();
auto intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
j = i++;
}
qDebug() << inside;
return inside;
}
};
///////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget root;
QHBoxLayout layout{&root};
for (int i = 0; i < 10; ++i) {
auto p = new PolygonButtonWidget();
p->setMinimumSize(100, 100);
p->setMNrButtons(i);
layout.addWidget(p);
}
root.setStyleSheet("background-color: rgb(19,19,19);");
QScrollArea view;
view.setWidget(&root);
view.show();
app.exec();
}
#include "main.moc"
The problem arises when I'm trying to resize the window. In the moment of resizing, I want my widgets to keep their aspect ratio. But that's not going to happen.
I have scroll list of widgets which is looking like this (if it's expended on X way too much)
If I will scale it on Y-axis it's going to look like this.
After I've changed the resizeEvent now it's going to look something like this
or like this
How can I fix this? For some reason, some of my widgets are going to disappear, what should be my approach in order to fix this issue?
The problem is caused by the assumption that there's any mechanism that will automatically resize the widgets for you. There isn't. A QScrollArea acts as a layout barrier and any layouts inside of it are isolated from its size, and thus from any resize events.
You must resize the container widget (the one with blue outline on your diagram) yourself anytime the scroll area changes size, and you need first to prepare a test case for the widgets such that their size changes are properly managed when placed in the layout of your choice, and said layout is resized.
Finally, the pet peeve of mine: It's unlikely that you actually need the QMainWindow for anything. It's just a silly Qt Creator template. But unless you want an MDI interface and docking, you shouldn't be using the QMainWindow - and especially not when making a self-contained example. All you need here is QScrollArea as a top-level widget. That's literally all. Any QWidget can be a top-level window!
For future submissions, please provide all the code needed in a single main.cpp file that begins with #include <QtWidgets> and ends with #include "main.moc". You won't need any other includes for Qt classes, and you can write class definitions Java-style, with all the methods defined within the class declaration itself. This provides for short code - after all, a SO question isn't an Enterprise project. It's supposed to be minimal, and that really means that anything not necessary must be removed. No need for header files, multiple includes, nor other fluff - i.e. use Qt containers instead of C++ STL so that you don't need more includes etc.
Your example should look roughly as follows:
#include <QtWidgets>
class PolygonButtonWidget : public QAbstractButton {
Q_OBJECT
/* without seeing the code here, your question is unanswerable */
};
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QWidget root;
QHBoxLayout layout{&root};
PolygonButtonWidget buttons[10];
for (auto &button : buttons)
layout.addWidget(&button);
QScrollArea view;
view.setWidget(&root);
view.show();
app.exec();
view.takeWidget();
}
#include "main.moc"
Without such an example, your question is hard to answer, since:
How can we debug it? Debugging means using a debugger. If your code cannot be immediately compiled, then it's quite unlikely that someone will bother debugging it, and debugging by inspection is often error-prone.
How can we provide a tested answer if we'd have to first write the entire "test case" for it?
How can we know what's inside your button widget? The behavior of that widget does affect the ultimate solution.
It'd also help if you described a few use cases that you'd expect to work. That is, mock up (with a drawing) the state of the widgets before and after the view is resized, so that we can easily see what it is that you expect to happen. A lot of it is very easy to miss when explaining your needs in words. Use cases are a lingua franca of software specifications. If you don't use them, it's highly likely that you yourself don't know what behavior you expect in all cases.
I'm trying to adapt the Qt5.9 QML Oscilloscope example to have the graph data pushed from c++ rather than requested from QML. Below are the pertinent sections from the QML Oscilloscope example.
datasource.h:
#ifndef DATASOURCE_H
#define DATASOURCE_H
#include <QtCore/QObject>
#include <QtCharts/QAbstractSeries>
QT_BEGIN_NAMESPACE
class QQuickView;
QT_END_NAMESPACE
QT_CHARTS_USE_NAMESPACE
class DataSource : public QObject
{
Q_OBJECT
public:
explicit DataSource(QQuickView *appViewer, QObject *parent = 0);
Q_SIGNALS:
public slots:
void generateData(int type, int rowCount, int colCount);
void update(QAbstractSeries *series);
private:
QQuickView *m_appViewer;
QList<QVector<QPointF> > m_data;
int m_index;
};
#endif // DATASOURCE_H
datasource.cpp:
#include "datasource.h"
#include <QtCharts/QXYSeries>
#include <QtCharts/QAreaSeries>
#include <QtQuick/QQuickView>
#include <QtQuick/QQuickItem>
#include <QtCore/QDebug>
#include <QtCore/QtMath>
QT_CHARTS_USE_NAMESPACE
Q_DECLARE_METATYPE(QAbstractSeries *)
Q_DECLARE_METATYPE(QAbstractAxis *)
DataSource::DataSource(QQuickView *appViewer, QObject *parent) :
QObject(parent),
m_appViewer(appViewer),
m_index(-1)
{
qRegisterMetaType<QAbstractSeries*>();
qRegisterMetaType<QAbstractAxis*>();
generateData(0, 5, 1024);
}
void DataSource::update(QAbstractSeries *series)
{
if (series) {
QXYSeries *xySeries = static_cast<QXYSeries *>(series);
m_index++;
if (m_index > m_data.count() - 1)
m_index = 0;
QVector<QPointF> points = m_data.at(m_index);
// Use replace instead of clear + append, it's optimized for performance
xySeries->replace(points);
}
}
void DataSource::generateData(int type, int rowCount, int colCount)
{
// Remove previous data
m_data.clear();
// Append the new data depending on the type
for (int i(0); i < rowCount; i++) {
QVector<QPointF> points;
points.reserve(colCount);
for (int j(0); j < colCount; j++) {
qreal x(0);
qreal y(0);
switch (type) {
case 0:
// data with sin + random component
y = qSin(3.14159265358979 / 50 * j) + 0.5 + (qreal) rand() / (qreal) RAND_MAX;
x = j;
break;
case 1:
// linear data
x = j;
y = (qreal) i / 10;
break;
default:
// unknown, do nothing
break;
}
points.append(QPointF(x, y));
}
m_data.append(points);
}
}
main.cpp:
#include <QtWidgets/QApplication>
#include <QtQml/QQmlContext>
#include <QtQuick/QQuickView>
#include <QtQml/QQmlEngine>
#include <QtCore/QDir>
#include "datasource.h"
int main(int argc, char *argv[])
{
// Qt Charts uses Qt Graphics View Framework for drawing, therefore
QApplication must be used.
QApplication app(argc, argv);
QQuickView viewer;
// The following are needed to make examples run without having to install the module
// in desktop environments.
#ifdef Q_OS_WIN
QString extraImportPath(QStringLiteral("%1/../../../../%2"));
#else
QString extraImportPath(QStringLiteral("%1/../../../%2"));
#endif
viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
QString::fromLatin1("qml")));
//QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close);
viewer.setTitle(QStringLiteral("QML Oscilloscope"));
DataSource dataSource(&viewer);
viewer.rootContext()->setContextProperty("dataSource", &dataSource);
viewer.setSource(QUrl("qrc:/qml/qmloscilloscope/main.qml"));
viewer.setResizeMode(QQuickView::SizeRootObjectToView);
viewer.setColor(QColor("#404040"));
viewer.show();
return app.exec();
}
ScopeView.qml:
import QtQuick 2.0
import QtCharts 2.1
ChartView {
id: chartView
animationOptions: ChartView.NoAnimation
theme: ChartView.ChartThemeDark
property bool openGL: true
property bool openGLSupported: true
onOpenGLChanged: {
if (openGLSupported) {
series("signal 1").useOpenGL = openGL;
}
}
Component.onCompleted: {
if (!series("signal 1").useOpenGL) {
openGLSupported = false
openGL = false
}
}
ValueAxis {
id: axisY1
min: -1
max: 4
}
ValueAxis {
id: axisX
min: 0
max: 1024
}
LineSeries {
id: lineSeries1
name: "signal 1"
axisX: axisX
axisY: axisY1
useOpenGL: chartView.openGL
}
Timer {
id: refreshTimer
interval: 1 / 60 * 1000 // 60 Hz
running: true
repeat: true
onTriggered: {
dataSource.update(chartView.series(0));
}
}
}
Rather than using the Timer in QML, I'd like to use an existing Timeout in a c++ class to push new data to the QML ChartView. I have two questions:
How would I achieve this for the QML Oscilloscope example posted above?
What format would be most suitable for the c++ data to facilitate this? I'm thinking a QVector of some sort; the data will be an integer or float with a vector index.
As you say in a comment you need to pass a series, then we create a method that receives the series and saves it in a member of the C ++ class, We also create a QTimer, and we do the same to update the interval:
*.h
public:
Q_INVOKABLE void setSeries(QAbstractSeries *series);
Q_INVOKABLE void setInterval(int interval);
[...]
private:
QXYSeries *mSeries;
QTimer *timer;
[...]
*.cpp
void DataSource::setSeries(QAbstractSeries *series)
{
if (series) {
mSeries = static_cast<QXYSeries *>(series);
}
}
Then we remove the update argument and use mSeries:
DataSource::DataSource(QQuickView *appViewer, QObject *parent) :
QObject(parent),
m_appViewer(appViewer),
m_index(-1)
{
[...]
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &DataSource::update);
timer->start(1 / 60 * 1000 );
}
void DataSource::update()
{
if (mSeries) {
m_index++;
if (m_index > m_data.count() - 1)
m_index = 0;
QVector<QPointF> points = m_data.at(m_index);
// Use replace instead of clear + append, it's optimized for performance
mSeries->replace(points);
}
}
void DataSource::setInterval(int interval)
{
if(timer){
if(timer->isActive())
timer->stop();
timer->start(interval);
}
}
*.qml
Component.onCompleted: {
dataSource.setSeries(chartView.series(0));
if (!series("signal 1").useOpenGL) {
openGLSupported = false
openGL = false
}
}
[...]
function changeRefreshRate(rate) {
dataSource.setInterval(1 / Number(rate) * 1000);
//refreshTimer.interval = 1 / Number(rate) * 1000;
}
You can find the complete example in the following link.
I have this example code:
QDialog *dialog = new QDialog(this);
QPoint dialogPos = dialog->mapToGlobal(dialog->pos());
QPoint thisPos = mapToGlobal(this->pos());
dialog->exec();
But the Dialog is not centered on his parent. Thanks in advance.
UPDATE:
I'm calling Dialog from constructor in MainWindow:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->panelInferior = new WidgetTabsInferior;
this->acciones = new Acciones(this);
crearAcciones();
crearBarraMenu();
crearToolbar();
crearTabsEditor();
crearArbolDir();
crearDockWindows();
crearStatusBar();
setWindowIcon(QIcon(":imgs/logo.png"));
connect(this->pestanasEditor , SIGNAL(currentChanged(int)),this,SLOT(cambioTab(int)));
this->dialogo = new AcercaDe(this);
this->dialogo->move(x() + (width() - dialogo->width()) / 2,
y() + (height() - dialogo->height()) / 2);
this->dialogo->show();
this->dialogo->raise();
this->dialogo->activateWindow();
}
But I get is:
I have this code in github
inline void CenterWidgets(QWidget *widget, QWidget *host = 0) {
if (!host)
host = widget->parentWidget();
if (host) {
auto hostRect = host->geometry();
widget->move(hostRect.center() - widget->rect().center());
}
else {
QRect screenGeometry = QApplication::desktop()->screenGeometry();
int x = (screenGeometry.width() - widget->width()) / 2;
int y = (screenGeometry.height() - widget->height()) / 2;
widget->move(x, y);
}
}
Hope it helps
edit
fix the deprecation warning issued from recent Qt versions:
#include <QScreen>
#include <QWidget>
#include <QGuiApplication>
inline void CenterWidgets(QWidget *widget, QWidget *host = Q_NULLPTR) {
if (!host)
host = widget->parentWidget();
if (host) {
auto hostRect = host->geometry();
widget->move(hostRect.center() - widget->rect().center());
}
else {
QRect screenGeometry = QGuiApplication::screens()[0]->geometry();
int x = (screenGeometry.width() - widget->width()) / 2;
int y = (screenGeometry.height() - widget->height()) / 2;
widget->move(x, y);
}
}
Thought I would post my own solution here. #CapelliC's solution works, but is deprecated since Qt5.11. Infact, the documentation says the QDesktopWidget class is obsolete.
Solution (which is a litte crude) is to use the QGuiApplication::screenAt()
Context: class inheriting QMainWindow, but can can extend for any QWidget
// Get current screen size
QRect rec = QGuiApplication::screenAt(this->pos())->geometry();
// Using minimum size of window
QSize size = this->minimumSize();
// Set top left point
QPoint topLeft = QPoint((rec.width() / 2) - (size.width() / 2), (rec.height() / 2) - (size.height() / 2));
// set window position
setGeometry(QRect(topLeft, size));
Hope it helps.
You have to change the geometry of the QDialog:
dialog->move(x() + (width() - dialog->width()) / 2,
y() + (height() - dialog->height()) / 2);
The move() function moves respect the parent, so it is not necessary to map to global.
On constructor the position and size of parent are not set yet. You can try executing the dialog in a separate method or, if needed on constructor, try with something like
QTimer::singleShot(0, [=]() {
// ... your dialog code
});
It will be shown on the next iteration of the event loop.
I think it's a Qt4 bug. I used Qt4 on Ubuntu and it doesn't respect the parent widget center.
However, when I use Qt5, It seems to work fine.
You can also use move() to arrive to your position.
This is the output of what I have done till now:
Although this shows only black and white images, the code works for color images too. The code basically populates the larger image using smaller images from a small database.
So this is where I am stuck, rather clueless.
How can I get an effect like this one. or this one.
Qn1 : I need to take another input(large) image*(One for which the effect is to be seen)* and merge them, but how?
Qn2 : How can I evaluate the goodness of the photo-mosaic? I have a genetic algorithm written for this but am unable to fix the fitness function,(mutation and crossover work perfectly).
This is what I could think of(for Qn1):
1. Take alternate pixels of the image shown above and the image for which the mosaic has to be made.
2. Take average of the pixel values of the above and input image for which the mosaic has to be made.
But have no clue to evaluate the goodness.
Below is a self contained sketch. The mosaicing algorithm is mid-way through the algorithms implemented in an excellent reference. It works well enough for two hours of work, I think. I tried for the code to be reasonably correct, with two caveats, left, as they say, as an exercise to the reader.
I'm not tracking the worker threads - if you try to exit the application while workers are active, it is expected to crash on exit. This is not nice, but otherwise benign and doesn't affect the overall functionality. There may be a few corrupt images left on disk, but those should be ignored when reloading.
There is no scaling of the image displayed in the label. The window will resize to the image size.
The tile image database can be filled with random images from imgur, you can also fill it with your own images by storing them on disk yourself. It's located in a standard application data path suffixed by /so-photomosaic/image. The fetched images are added there. Upon startup, the image database is repopulated from disk in the background - that's how your own tile images would be loaded. In fact, all of image processing is done in non-GUI threads. On a rather unassuming 5 year old Core 2 OS X system, disk image loading proceeds at about 5000 images/s. The images requested from imgur are their small size, or 90x90.
The tile matching is done with a 4x4 subdivision grid (divs parameter to calcPropsFor). The images are downsampled to a 4x4 mosaic, and the RGB color values of consecutive pixels in that grid are stored in Props vectors. The squared sums of differences of elements of those vectors are the measure of fit. For each tile to be replaced, the images are sorted according to their fit, and one of the best ones are picked up at random. The randomness parameter is a power-of-to of the sample size from which the image is randomly selected.
It uses Qt 5 and C++11. Length: 300 lines, out of which 64 are the random image source, 25 are the disk image database, and 88 are actually to do with mosaics. The image processing code would probably look and perform better if OpenCV or Eigen was used instead of valarray/QImage, but oh well.
Also, all of this would be probably 50 lines in Mathematica :)
# main.pro
# Make sure to re-run quake once this is set.
TEMPLATE = app
QT += widgets network concurrent
CONFIG += c++11
SOURCES += main.cpp
TARGET = photomosaic
#include <QApplication>
#include <QLabel>
#include <QSlider>
#include <QPushButton>
#include <QCheckBox>
#include <QBoxLayout>
#include <QFileDialog>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QRegularExpression>
#include <QImage>
#include <QPainter>
#include <QColor>
#include <QAtomicInt>
#include <QMutex>
#include <QtConcurrent>
#include <QStandardPaths>
#include <algorithm>
#include <functional>
#include <valarray>
/// Provides random images. There may be more than one response per request.
class RandomImageSource : public QObject {
Q_OBJECT
int m_parallelism;
bool m_auto;
QNetworkAccessManager m_mgr;
QSet<QNetworkReply*> m_replies;
QList<QUrl> m_deferred;
QRegularExpression m_imgTagRE, m_imgUrlRE;
QUrl m_randomGallery;
void get(const QUrl & url) {
if (m_replies.count() < m_parallelism) {
QNetworkRequest req(url);
req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
m_replies.insert(m_mgr.get(req));
} else
m_deferred << url;
}
void finishReply(QNetworkReply * reply) {
m_replies.remove(reply);
if (reply) reply->deleteLater();
if (! m_deferred.isEmpty()) get(m_deferred.takeLast());
while (m_deferred.isEmpty() && m_auto) get(m_randomGallery);
}
Q_SLOT void rsp(QNetworkReply * reply) {
auto loc = reply->header(QNetworkRequest::LocationHeader);
if (loc.isValid()) {
get(loc.toUrl()); // redirect
} else {
auto ct = reply->header(QNetworkRequest::ContentTypeHeader).toString();
if (ct.startsWith("text/html"))
foreach (QUrl url, parseImageUrls(reply->readAll()))
get(url);
else if (ct.startsWith("image")) {
auto img = QImage::fromData(reply->readAll());
img.setText("filename", m_imgUrlRE.match(reply->url().toString()).captured(1));
if (!img.isNull()) emit rspImage(img);
}
}
finishReply(reply);
}
QList<QUrl> parseImageUrls(const QByteArray & html) {
QList<QUrl> urls;
auto it = m_imgTagRE.globalMatch(QString::fromUtf8(html));
while (it.hasNext()) { auto match = it.next(); // get small images
urls << QUrl("http:" + match.captured(1) + "s" + match.captured(2)); }
return urls;
}
public:
RandomImageSource(QObject * parent = 0) : QObject (parent),
m_parallelism(20), m_auto(false),
m_imgTagRE("<img src=\"(//i\\.imgur\\.com/[^.]+)(\\.[^\"]+)\""),
m_imgUrlRE("http://i\\.imgur\\.com/(.+)$"),
m_randomGallery("http://imgur.com/gallery/random")
{
connect(&m_mgr, SIGNAL(finished(QNetworkReply*)), SLOT(rsp(QNetworkReply*)));
}
Q_SLOT void reqImages(int count) {
while (count--) get(m_randomGallery);
}
Q_SIGNAL void rspImage(const QImage &);
bool automatic() const { return m_auto; }
Q_SLOT void setAutomatic(bool a) { if ((m_auto = a)) finishReply(0); }
int parallelism() const { return m_parallelism; }
Q_SLOT void setParallelism(int p) { m_parallelism = p; if (m_auto) finishReply(0); }
};
/// Stores images on disk, and loads them in the background.
class ImageStorage : public QObject {
Q_OBJECT
QString const m_path;
public:
ImageStorage() :
m_path(QStandardPaths::writableLocation(QStandardPaths::DataLocation)
+ "/images/")
{ QDir().mkpath(m_path); }
Q_SLOT void addImage(const QImage & img) {
QString path = img.text("filename");
if (path.isEmpty()) return;
path.prepend(m_path);
QtConcurrent::run([img, path]{ img.save(path); });
}
Q_SLOT void retrieveAll() {
QString const path = m_path;
QtConcurrent::run([this, path] {
QStringList const images = QDir(path).entryList(QDir::Files);
foreach (QString image, images) QtConcurrent::run([this, image, path] {
QImage img; if (img.load(path + image)) emit retrieved(img);
});
});
}
Q_SIGNAL void retrieved(const QImage &);
};
/// A memory database of images. Finds best match to a given image.
class ImageDatabase : public QObject {
Q_OBJECT
typedef std::valarray<qreal> Props;
typedef QPair<QImage, Props> ImageProps;
QMutex mutable m_mutex;
QList<ImageProps> m_images;
static void inline addProps(Props & p, int i, QRgb rgb) {
QColor const c = QColor::fromRgb(rgb);
p[i+0] += c.redF(); p[i+1] += c.greenF(); p[i+2] += c.blueF();
}
static Props calcPropsFor(const QImage & img, int divs = 4) {
Props props(0.0, 3 * divs * divs);
std::valarray<int> counts(0, divs * divs);
QSize div = img.size() / divs;
for (int y = 0; y < img.height(); ++y)
for (int x = 0; x < img.width(); ++x) {
int slice = x/div.width() + (y*divs/div.height());
if (slice >= divs*divs) continue;
addProps(props, slice*3, img.pixel(x, y));
counts[slice] ++;
}
for (size_t i = 0; i < props.size(); ++i) props[i] /= counts[i/3];
return props;
}
public:
Q_SIGNAL void newImageCount(int);
Q_SLOT void addImage(const QImage & img) {
QtConcurrent::run([this, img]{
Props props = calcPropsFor(img);
QMutexLocker lock(&m_mutex);
m_images << qMakePair(img, props);
int count = m_images.count();
lock.unlock();
emit newImageCount(count);
});
}
ImageProps bestMatchFor(const QImage & img, int randLog2) const {
QMutexLocker lock(&m_mutex);
QList<ImageProps> const images = m_images;
lock.unlock();
Props const props = calcPropsFor(img);
typedef QPair<qreal, const ImageProps *> Match;
QList<Match> matches; matches.reserve(images.size());
std::transform(images.begin(), images.end(), std::back_inserter(matches),
[props](const ImageProps & prop){
return qMakePair(pow(props - prop.second, 2).sum(), &prop);
});
std::sort(matches.begin(), matches.end(),
[](Match a, Match b) { return b.first < a.first; });
randLog2 = 1<<randLog2;
return *(matches.end()-randLog2+qrand()%randLog2)->second;
}
};
QImage getMosaic(QImage img, const ImageDatabase & db, int size, int randLog2)
{
QPainter p(&img);
for (int y = 0; y < img.height(); y += size)
for (int x = 0; x < img.width(); x += size) {
QImage r = db.bestMatchFor(img.copy(x, y, size, size), randLog2).first
.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
p.drawImage(x, y, r);
}
return img;
}
class MosaicGenerator : public QObject {
Q_OBJECT
QPointer<ImageDatabase> m_db;
int m_size, m_randLog2;
QAtomicInt m_busy;
QImage m_image;
void update() {
if (m_image.isNull() || m_busy.fetchAndAddOrdered(1)) return;
QImage image = m_image;
QtConcurrent::run([this, image]{ while (true) {
emit hasMosaic(getMosaic(image, *m_db, m_size, m_randLog2));
if (m_busy.testAndSetOrdered(1, 0)) return;
m_busy.fetchAndStoreOrdered(1);
}});
}
public:
MosaicGenerator(ImageDatabase * db) : m_db(db), m_size(16), m_randLog2(0) {}
Q_SLOT void setImage(const QImage & img) { m_image = img; update(); }
Q_SLOT void setSize(int s) { m_size = s; update(); }
Q_SLOT void setRandLog2(int r) { m_randLog2 = r; update(); }
Q_SIGNAL void hasMosaic(const QImage &);
};
class Window : public QWidget {
Q_OBJECT
bool m_showSource;
QImage m_source, m_mosaic;
QBoxLayout m_layout;
QSlider m_parallelism, m_cellSize, m_randomness;
QLabel m_imgCount, m_parCount, m_image;
QPushButton m_add, m_load, m_toggle;
MosaicGenerator m_gen;
Q_SIGNAL void newSource(const QImage &);
void updateImage() {
const QImage & img = m_showSource ? m_source : m_mosaic;
m_image.setPixmap(QPixmap::fromImage(img));
}
public:
Window(ImageDatabase * db, QWidget * parent = 0) : QWidget(parent),
m_showSource(true), m_layout(QBoxLayout::TopToBottom, this),
m_parallelism(Qt::Horizontal), m_cellSize(Qt::Horizontal),
m_randomness(Qt::Horizontal), m_add("Fetch Images"),
m_load("Open for Mosaic"), m_toggle("Toggle Mosaic"), m_gen(db)
{
QBoxLayout * row = new QBoxLayout(QBoxLayout::LeftToRight);
row->addWidget(new QLabel("Images in DB:"));
row->addWidget(&m_imgCount);
row->addWidget(new QLabel("Fetch parallelism:"));
row->addWidget(&m_parallelism);
row->addWidget(&m_parCount);
row->addWidget(&m_add);
m_parallelism.setRange(1, 100);
m_layout.addLayout(row);
m_layout.addWidget(&m_image);
row = new QBoxLayout(QBoxLayout::LeftToRight);
row->addWidget(new QLabel("Cell Size:"));
row->addWidget(&m_cellSize);
row->addWidget(new QLabel("Randomness:"));
row->addWidget(&m_randomness);
m_cellSize.setRange(4, 64); m_cellSize.setTracking(false);
m_randomness.setRange(0,6); m_randomness.setTracking(false);
m_layout.addLayout(row);
row = new QBoxLayout(QBoxLayout::LeftToRight);
row->addWidget(&m_load);
row->addWidget(&m_toggle);
m_layout.addLayout(row);
m_add.setCheckable(true);
m_parCount.connect(&m_parallelism, SIGNAL(valueChanged(int)), SLOT(setNum(int)));
connect(&m_add, SIGNAL(clicked(bool)), SIGNAL(reqAutoFetch(bool)));
connect(&m_parallelism, SIGNAL(valueChanged(int)), SIGNAL(reqParallelism(int)));
m_gen.connect(&m_cellSize, SIGNAL(valueChanged(int)), SLOT(setSize(int)));
m_gen.connect(&m_randomness, SIGNAL(valueChanged(int)), SLOT(setRandLog2(int)));
m_parallelism.setValue(20);
m_cellSize.setValue(16);
m_randomness.setValue(4);
connect(&m_load, &QPushButton::clicked, [this]{
QString file = QFileDialog::getOpenFileName(this);
QtConcurrent::run([this, file]{
QImage img; if (!img.load(file)) return;
emit newSource(img);
});
});
connect(this, &Window::newSource, [this](const QImage &img){
m_source = m_mosaic = img; updateImage(); m_gen.setImage(m_source);
});
connect(&m_gen, &MosaicGenerator::hasMosaic, [this](const QImage &img){
m_mosaic = img; updateImage();
});
connect(&m_toggle, &QPushButton::clicked, [this]{
m_showSource = !m_showSource; updateImage();
});
}
Q_SLOT void setImageCount(int n) { m_imgCount.setNum(n); }
Q_SIGNAL void reqAutoFetch(bool);
Q_SIGNAL void reqParallelism(int);
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
a.setOrganizationDomain("stackoverflow.com");
a.setApplicationName("so-photomosaic");
RandomImageSource src;
ImageDatabase db;
ImageStorage stg;
Window ui(&db);
db.connect(&src, SIGNAL(rspImage(QImage)), SLOT(addImage(QImage)));
stg.connect(&src, SIGNAL(rspImage(QImage)), SLOT(addImage(QImage)));
db.connect(&stg, SIGNAL(retrieved(QImage)), SLOT(addImage(QImage)));
ui.connect(&db, SIGNAL(newImageCount(int)), SLOT(setImageCount(int)));
src.connect(&ui, SIGNAL(reqAutoFetch(bool)), SLOT(setAutomatic(bool)));
src.connect(&ui, SIGNAL(reqParallelism(int)), SLOT(setParallelism(int)));
stg.retrieveAll();
ui.show();
return a.exec();
}
#include "main.moc"