I enjoy using Qt3D, but all of the examples I see for it are full window applications. What I can't understand from the examples is how to add a qt3d rendering window to a regular qt gui application.
Basically what I want is a little rendering widget for my Qt5 Gui application.
I've looked into Qtgl widget, but I really want to use the scene management abilities of Qt3D.
How can I render as a sub window inside of a qt Gui window?
Is this possible?
Update
So I added this to my MainWindow.cpp It is loosely based off of this https://www.qt.io/blog/2013/02/19/introducing-qwidgetcreatewindowcontainer
LoadModelView *view = new LoadModelView(); //Crashes on this. Will not compile with
// LoadModelView(this)
QWidget *container = QWidget::createWindowContainer(view);
container->setFocusPolicy(Qt::TabFocus);
ui->gridLayout->addWidget(container);
which seems right.
my load_model.cpp begins like this:
#include "qglmaterialcollection.h"
#include "qglmaterial.h"
#include "qglscenenode.h"
#include "qgllightmodel.h"
#include "qglabstractscene.h"
#include <QtGui/qmatrix4x4.h>
#include <QPropertyAnimation>
#include <QtCore/qmath.h>
#define DEGREE_TO_RAD (3.1415926/180.0)
LoadModelView::LoadModelView(QWindow *parent)
: QGLView(parent)
, m_pSTLScene(0)
{
loadModels();
camera()->setCenter(QVector3D(0, 0, 0));
camera()->setEye(QVector3D(0, 4, 10));
}
LoadModelView::~LoadModelView()
{
delete m_pSTLScene;
}
void LoadModelView::paintGL(QGLPainter *painter)
{
QMatrix4x4 stlWorld;
stlWorld.setToIdentity();
stlWorld.scale(0.1);
stlWorld.translate(QVector3D(2.0,0.0,0.0));
painter->setStandardEffect(QGL::LitMaterial);
painter->setFaceColor(QGL::AllFaces,QColor(170,202,0));
painter->modelViewMatrix() = camera()->modelViewMatrix() * stlWorld;
m_pSTLScene->mainNode()->draw(painter);
}
void FixNodesRecursive(int matIndex, QGLSceneNode* pNode)
{
if (pNode) {
pNode->setMaterialIndex(matIndex);
// pNode->setEffect(QGL::FlatReplaceTexture2D);
foreach (QGLSceneNode* pCh, pNode->children()) {
FixNodesRecursive(matIndex, pCh);
}
}
}
void LoadModelView::loadModels()
{
{
m_pSTLScene = QGLAbstractScene::loadScene(QLatin1String(":/models/Sheep.stl"), QString(),"CorrectNormals CorrectAcute");
Q_ASSERT(m_pSTLScene!=0);
QGLMaterial *mat = new QGLMaterial;
mat->setAmbientColor(QColor(170,202,0));
mat->setDiffuseColor(QColor(170,202,0));
mat->setShininess(128);
QGLSceneNode* pSTLSceneRoot = m_pSTLScene->mainNode();
int matIndex = pSTLSceneRoot->palette()->addMaterial(mat);
pSTLSceneRoot->setMaterialIndex(matIndex);
pSTLSceneRoot->setEffect(QGL::FlatReplaceTexture2D);
FixNodesRecursive(matIndex,pSTLSceneRoot);
}
}
It crashes with:
This application has requested the runtime to terminate it in an unusual way.
And in the qt application output:
Invalid parameter passed to C runtime function.
EDIT Added the rest of the class in question
I notice that in the example I am adapting https://github.com/Distrotech/qt3d/blob/master/tutorials/qt3d/penguin/main.cpp the window is initialized by saying:
LoadModelView view;
However, saying
LoadModelView *view = new LoadModelView(this)
crashes
You can subclass QGLView class which extends QGLWidget with support for 3D viewing:
class GLView : public QGLView
{
Q_OBJECT
public:
GLView(QWidget *parent = 0);
~GLView();
protected:
void initializeGL(QGLPainter *painter);
void paintGL(QGLPainter *painter);
private:
QGLAbstractScene *m_scene;
QGLSceneNode *m_rootNode;
};
GLView::GLView(QWidget *parent)
: QGLView(parent)
, m_scene(0)
, m_rootNode(0)
{
// Viewing Volume
camera()->setFieldOfView(25);
camera()->setNearPlane(1);
camera()->setFarPlane(1000);
// Position of the camera
camera()->setEye(QVector3D(0, 3, 4));
// Direction that the camera is pointing
camera()->setCenter(QVector3D(0, 3, 0));
}
GLView::~GLView()
{
delete m_scene;
}
void GLView::initializeGL(QGLPainter *painter)
{
// Background color
painter->setClearColor(QColor(70, 70, 70));
// Load the 3d model from the file
m_scene = QGLAbstractScene::loadScene("models/model1/simplemodel.obj");
m_rootNode = m_scene->mainNode();
}
void GLView::paintGL(QGLPainter *painter)
{
m_rootNode->draw(painter);
}
Qt 5.1 introduces the function QWidget::createWindowContainer(). A function that creates a QWidget wrapper for an existing QWindow, allowing it to live inside a QWidget-based application. You can use QWidget::createWindowContainer which creates a QWindow in a QWidget. This allows placing QWindow-subclasses in Widget-Layouts. This way you
can embed your QGLView inside a widget.
This is how I did it on Qt5.10. This example shows a scene with a cuboid. Scene you can than use like a button or so... To use this add QT += 3dextras to your project file.
szene.h
#ifndef SCENE_H
#define SCENE_H
#include <QObject>
#include <QWidget>
class Scene
: public QWidget
{
Q_OBJECT
private:
QWidget *container;
public:
explicit Scene(QWidget *parent = nullptr);
protected:
// reimplementation needed to handle resize events
// http://doc.qt.io/qt-5/qwidget.html#resizeEvent
void
resizeEvent ( QResizeEvent * event );
public slots:
void
resizeView(QSize size);
};
#endif // SCENE_H
scene.cpp
#include "scene.h"
#include <Qt3DExtras/Qt3DWindow>
#include <Qt3DExtras/QForwardRenderer>
#include <QQuaternion>
#include <Qt3DCore/QEntity>
#include <Qt3DCore/QTransform>
#include <Qt3DRender/QCamera>
#include <Qt3DExtras/QCuboidMesh>
#include <Qt3DExtras/QPhongMaterial>
Scene::Scene(QWidget *parent)
: QWidget(parent)
{
auto view = new Qt3DExtras::Qt3DWindow();
// create a container for Qt3DWindow
container = createWindowContainer(view,this);
// background color
view->defaultFrameGraph()->setClearColor(QColor(QRgb(0x575757)));
// Root entity
auto rootEntity = new Qt3DCore::QEntity();
// Camera
auto camera = new Camera(rootEntity,view);
auto cameraEntity = view->camera();
cameraEntity->setPosition(QVector3D(0, 0, 50.0f));
cameraEntity->setUpVector(QVector3D(0, 1, 0));
cameraEntity->setViewCenter(QVector3D(0, 0, 0));
// Cuboid
auto cuboidMesh = new Qt3DExtras::QCuboidMesh();
// CuboidMesh Transform
auto cuboidTransform = new Qt3DCore::QTransform();
cuboidTransform->setScale(10.0f);
cuboidTransform->setTranslation(QVector3D(0.0f, 0.0f, 0.0f));
cuboidTransform->setRotation(QQuaternion(1,1.5,1,0).normalized());
auto cuboidMaterial = new Qt3DExtras::QPhongMaterial();
cuboidMaterial->setDiffuse(QColor(QRgb(0x005FFF)));
// assamble entity
auto cuboidEntity = new Qt3DCore::QEntity(rootEntity);
cuboidEntity->addComponent(cuboidMesh);
cuboidEntity->addComponent(cuboidMaterial);
cuboidEntity->addComponent(cuboidTransform);
// Set root object of the scene
view->setRootEntity(rootEntity);
}
void
Scene::resizeView(QSize size)
{
container->resize(size);
}
void
Scene::resizeEvent ( QResizeEvent * /*event*/ )
{
resizeView(this->size());
}
Related
I'm trying to create an application using Qt3D that where I can create multiple view windows on the same scene. I started with the code from the Qt3DWindow and the Simple C++ Example and started moving things around. What I figured is that each view window would define its own frame graph (just using a simple QForwardRenderer for now) and camera and then I would add each window's frame graph to the main frame graph in my scene.
Everything appears to be working fine as I create multiple windows, but when I close the windows and start removing frame graphs, the application crashes. It's crashing on a background thread somewhere down in the Qt3DCore or Qt3DRender module and I can't get to the source code. As I understand it I should be able to modify the frame graph dynamically at run time, but is that not thread safe? Are you expected to wholesale replace one frame graph with another as opposed to modifying the active frame graph like I'm doing?
--- Edit ---
I did a little more testing and if I delay destroying the QWindow (i.e., the surface that it's trying to render to) a bit after removing its frame graph from the parent frame graph, I don't get the crash. I do however get some warnings on the console that say:
Qt3D.Renderer.Backend: bool __cdecl Qt3DRender::Render::GraphicsContext::makeCurrent(class QSurface *) makeCurrent failed
My guess is it's a threading issue, that the backend is still trying to use the QSurface to render to after it has been destroyed on the main thread. I don't really like my solution (I just used a single shot timer to delay destroying the window by 1 second), but it's better than crashing.
RenderWindow.h
#ifndef RENDERWINDOW_H
#define RENDERWINDOW_H
#include <QWindow>
#include <Qt3DCore>
#include <Qt3DRender>
#include <Qt3DInput>
#include <Qt3DExtras/QForwardRenderer>
class RenderWindow : public QWindow
{
public:
RenderWindow(QScreen* screen = nullptr);
~RenderWindow();
Qt3DRender::QCamera* camera() const;
Qt3DRender::QFrameGraphNode* frameGraph() const;
protected:
void resizeEvent(QResizeEvent *) override;
private:
// Rendering
Qt3DRender::QFrameGraphNode* mpFrameGraph;
Qt3DRender::QCamera* mpCamera;
static bool msFormatDefined;
};
#endif // RENDERWINDOW_H
RenderWindow.cpp
#include "renderwindow.h"
#include <QDebug>
bool RenderWindow::msFormatDefined = false;
namespace
{
// Different clear colors so that it's obvious each window is using a
// different camera and frame graph.
static QColor sClearColors[] = {
Qt::darkBlue,
Qt::blue,
Qt::darkCyan,
Qt::cyan
};
static int sViewCount = 0;
}
RenderWindow::RenderWindow(QScreen* screen)
: QWindow(screen)
, mpFrameGraph(nullptr)
, mpCamera(new Qt3DRender::QCamera)
{
setSurfaceType(QWindow::OpenGLSurface);
// Set the default surface format once
if (!msFormatDefined)
{
QSurfaceFormat format;
format.setVersion(4, 3);
format.setProfile(QSurfaceFormat::CoreProfile);
format.setDepthBufferSize(24);
format.setSamples(4);
format.setStencilBufferSize(8);
setFormat(format);
QSurfaceFormat::setDefaultFormat(format);
msFormatDefined = true;
}
// Camera
mpCamera->lens()->setPerspectiveProjection(45.0f, 16.0f/9.0f, 0.1f, 1000.0f);
mpCamera->setPosition(QVector3D(0, 0, 40.0f));
mpCamera->setViewCenter(QVector3D(0, 0, 0));
// Frame Graph (using forward renderer for now)
Qt3DExtras::QForwardRenderer* renderer = new Qt3DExtras::QForwardRenderer;
renderer->setCamera(mpCamera);
renderer->setSurface(this);
renderer->setClearColor(sClearColors[sViewCount++ % 4]);
mpFrameGraph = renderer;
}
RenderWindow::~RenderWindow()
{
qDebug() << "start ~RenderWindow";
// Unparent objects. Probably not necessary but it makes me feel
// good inside.
mpFrameGraph->setParent(static_cast<Qt3DCore::QNode*>(nullptr));
mpCamera->setParent(static_cast<Qt3DCore::QNode*>(nullptr));
delete mpFrameGraph;
delete mpCamera;
qDebug() << "end ~RenderWindow";
}
Qt3DRender::QCamera* RenderWindow::camera() const
{
return mpCamera;
}
Qt3DRender::QFrameGraphNode* RenderWindow::frameGraph() const
{
return mpFrameGraph;
}
void RenderWindow::resizeEvent(QResizeEvent *)
{
mpCamera->setAspectRatio((float)width()/(float)height());
}
Scene.h
#ifndef SCENE_H
#define SCENE_H
#include <Qt3DCore/QEntity>
#include <Qt3DInput/QInputAspect>
#include <Qt3DRender/QFrameGraphNode>
#include <Qt3DRender/QRenderAspect>
#include <Qt3DRender/QRenderSettings>
class RenderWindow;
class Scene
{
public:
Scene();
~Scene();
Qt3DCore::QEntityPtr rootNode() const;
void addView(RenderWindow* window);
private:
void setupScene();
private:
Qt3DCore::QEntityPtr mpRoot;
// Frame Graph
Qt3DRender::QFrameGraphNode* mpFrameGraph;
Qt3DRender::QRenderSettings* mpRenderSettings;
// Aspects
Qt3DCore::QAspectEngine* mpEngine;
Qt3DRender::QRenderAspect* mpRenderAspect;
Qt3DInput::QInputAspect* mpInputAspect;
};
#endif // SCENE_H
Scene.cpp
#include "scene.h"
#include <QDebug>
#include <QPropertyAnimation>
#include <Qt3DCore/QTransform>
#include <Qt3DRender/QClearBuffers>
#include <Qt3DExtras/QPhongMaterial>
#include <Qt3DExtras/QCylinderMesh>
#include <Qt3DExtras/QSphereMesh>
#include <Qt3DExtras/QTorusMesh>
#include "orbittransformcontroller.h"
#include "RenderWindow.h"
Scene::Scene()
: mpRoot(nullptr)
, mpFrameGraph(new Qt3DRender::QFrameGraphNode)
, mpRenderSettings(new Qt3DRender::QRenderSettings)
, mpEngine(new Qt3DCore::QAspectEngine)
, mpRenderAspect(new Qt3DRender::QRenderAspect)
, mpInputAspect(new Qt3DInput::QInputAspect)
{
mpEngine->registerAspect(mpRenderAspect);
mpRenderSettings->setActiveFrameGraph(mpFrameGraph);
setupScene();
mpRoot->addComponent(mpRenderSettings);
mpEngine->setRootEntity(mpRoot);
}
Scene::~Scene()
{
qDebug() << "start ~Scene";
mpEngine->setRootEntity(Qt3DCore::QEntityPtr());
mpRoot.clear();
delete mpEngine;
// mpRenderSettings and mpFrameGraph are children of the
// root node and are automatically destroyed when it is.
qDebug() << "end ~Scene";
}
Qt3DCore::QEntityPtr Scene::rootNode() const
{
return mpRoot;
}
void Scene::addView(RenderWindow* window)
{
// Add the window's frame graph to the main frame graph
if (window->frameGraph())
{
window->frameGraph()->setParent(mpFrameGraph);
}
}
void Scene::setupScene()
{
mpRoot.reset(new Qt3DCore::QEntity);
Qt3DCore::QEntity* entity = new Qt3DCore::QEntity;
entity->setParent(mpRoot.data());
// Create the material
Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial(entity);
material->setAmbient(Qt::black);
material->setDiffuse(QColor(196, 196, 32));
material->setSpecular(Qt::white);
// Torrus
Qt3DCore::QEntity *torusEntity = new Qt3DCore::QEntity(entity);
Qt3DExtras::QTorusMesh *torusMesh = new Qt3DExtras::QTorusMesh;
torusMesh->setRadius(5);
torusMesh->setMinorRadius(1);
torusMesh->setRings(100);
torusMesh->setSlices(20);
Qt3DCore::QTransform *torusTransform = new Qt3DCore::QTransform;
torusTransform->setScale3D(QVector3D(1.5, 1, 0.5));
torusTransform->setRotation(QQuaternion::fromAxisAndAngle(QVector3D(1, 0, 0), -45.0f));
torusEntity->addComponent(torusMesh);
torusEntity->addComponent(torusTransform);
torusEntity->addComponent(material);
// Sphere
Qt3DCore::QEntity *sphereEntity = new Qt3DCore::QEntity(entity);
Qt3DExtras::QSphereMesh *sphereMesh = new Qt3DExtras::QSphereMesh;
sphereMesh->setRadius(3);
Qt3DCore::QTransform *sphereTransform = new Qt3DCore::QTransform;
/*OrbitTransformController *controller = new OrbitTransformController(sphereTransform);
controller->setTarget(sphereTransform);
controller->setRadius(20.0f);
QPropertyAnimation *sphereRotateTransformAnimation = new QPropertyAnimation(sphereTransform);
sphereRotateTransformAnimation->setTargetObject(controller);
sphereRotateTransformAnimation->setPropertyName("angle");
sphereRotateTransformAnimation->setStartValue(QVariant::fromValue(0));
sphereRotateTransformAnimation->setEndValue(QVariant::fromValue(360));
sphereRotateTransformAnimation->setDuration(10000);
sphereRotateTransformAnimation->setLoopCount(-1);
sphereRotateTransformAnimation->start();*/
sphereEntity->addComponent(sphereMesh);
sphereEntity->addComponent(sphereTransform);
sphereEntity->addComponent(material);
}
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "scene.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void createWindow();
private:
Ui::MainWindow *ui;
Scene* scene;
};
#endif // MAINWINDOW_H
MainWindow.cpp
#include "mainwindow.h"
#include <QDebug>
#include "ui_mainwindow.h"
#include "renderwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
scene(new Scene())
{
ui->setupUi(this);
connect(ui->createButton, &QPushButton::clicked, this, &MainWindow::createWindow);
}
MainWindow::~MainWindow()
{
qDebug() << "~MainWindow";
delete scene;
delete ui;
}
void MainWindow::createWindow()
{
RenderWindow* window = new RenderWindow();
scene->addView(window);
window->resize(640, 480);
window->show();
QVector3D pos[] = {
QVector3D(0, 0, 40),
QVector3D(0, 25, -30),
QVector3D(-20, -20, -20),
QVector3D(40, 0, 0)
};
static int count = 0;
window->camera()->setPosition(pos[count++%4]);
window->camera()->setViewCenter(QVector3D(0, 0, 0));
// Delete the window when it is closed.
connect(window, &QWindow::visibilityChanged, this, [=](bool on)
{
if (!on)
window->deleteLater();
});
}
I've thoroughly tested your example and draw the same conclusions. When you destroy the window too quickly, the application crashes, probably because Qt3D still tries to issue some OpenGL commands to the underlying QSurface. I think this is a bug that should be reported.
A 'cleaner' work-around of this problem could be to track the generated 3d windows in the main window. You could maintain a list of pointers to all windows that where generated (and probably closed by the user at the some point). The windows are finally destroyed in the destructor of the main window.
I had exactly the same problem. I was creating a class derived from Qt3DWindow in a dialog box so the user could preview the effects of the choices made, and the program crashed when the dialog exited. In fact on Windows this crash causes the debugger and Qt Creator to crash too!
I tried working around this in a variety of ways and some helped because it turns out that it is a threading issue that was fixed on the 23rd October:
https://github.com/qt/qt3d/commit/3314694004b825263c9b9f2d69bd85da806ccbbc
The fix is now to apply the patch, and recompile Qt. 5.11.3 (or perhaps 5.12) will be out quite soon I expect but this bug is a killer if you are using Qt3D in dialogs.
I'm trying to create a simple frame in Qt with a tick and some text. I made two new label implementations because I wanted the labels to dynamically fill all the available space but when I resize the window the sizes are off, as shown by the qDebug output, which represents the size of the image label:
Resized: 244 , 244 <-- Window first created
Resized: 305 , 305 <-- Window maximized
Resized: 135 , 135 <-- Window restored to original size
As you can see, when the window is restored to its original size the image is not. The last size should be 244, 244.
The code which describes the behaviour of the two widgets is the following:
"widgets.h":
/*
* This file includes many custom widgets.
*/
#ifndef APOCRYPHA_WIDGETS
#define APOCRYPHA_WIDGETS
#include <QWidget>
#include <QLabel>
#include <QTimer>
#include <QPixmap>
#include <QResizeEvent>
#include <QPaintEvent>
class AutoTextLabel : public QLabel {
Q_OBJECT
public:
explicit AutoTextLabel(QWidget* parent);
AutoTextLabel(QWidget* parent, QString text);
protected:
void resizeEvent(QResizeEvent* event) override;
private:
QTimer* resizeTimer;
private slots:
void onResizeEnd();
};
class AutoImageLabel : public QLabel {
Q_OBJECT
public:
explicit AutoImageLabel(QWidget* parent);
AutoImageLabel(QWidget* parent, const QPixmap& pixmap);
void setFillOrientation(int orientation);
QSize sizeHint() const override;
public slots:
void setPixmap(const QPixmap &newPix);
void resizeEvent(QResizeEvent* event) override;
protected:
// void paintEvent(QPaintEvent* event) override;
private:
int fillOrientation;
int widthForHeight(int h) const;
int heightForWidth(int w) const override;
QPixmap scaledPixmap() const;
QPixmap labelPixmap;
};
#endif //APOCRYPHA_WIDGETS
"widgets.cpp":
/*
* This file includes many custom widgets.
*/
#include "widgets.h"
#include <QPainter>
#include <QDebug>
AutoTextLabel::AutoTextLabel(QWidget *parent, QString text) : QLabel(text, parent){
// Enable antialiasing
QFont aaFont(font());
aaFont.setStyleStrategy(QFont::PreferAntialias);
setFont(aaFont);
// This timer is used to fire a slot when a window is resized
resizeTimer = new QTimer();
resizeTimer->setSingleShot(true);
connect(resizeTimer, SIGNAL(timeout()), SLOT(onResizeEnd()));
}
AutoTextLabel::AutoTextLabel(QWidget *parent) : AutoTextLabel(parent, "") {}
void AutoTextLabel::resizeEvent(QResizeEvent *event) {
QWidget::resizeEvent(event);
// Only fire when 25ms have passed since the last resize.
resizeTimer->start(25);
}
void AutoTextLabel::onResizeEnd() {
QFont updatedFont(font());
// Resize Text
if (!text().isEmpty()){
int fontSize = 1;
updatedFont.setPixelSize(fontSize);
QRect boundingRectangle;
// Update bounding rectangle
if (wordWrap())
boundingRectangle = QFontMetrics(updatedFont).boundingRect(contentsRect(), Qt::TextWordWrap, text());
else
boundingRectangle = QFontMetrics(updatedFont).boundingRect(text());
while (boundingRectangle.height() <= contentsRect().height()) {
fontSize++;
updatedFont.setPixelSize(fontSize);
// Update bounding rectangle
if (wordWrap())
boundingRectangle = QFontMetrics(updatedFont).boundingRect(contentsRect(), Qt::TextWordWrap, text());
else
boundingRectangle = QFontMetrics(updatedFont).boundingRect(text());
}
updatedFont.setPixelSize(fontSize - 1);
setFont(updatedFont);
}
}
/* Auto Image Label */
AutoImageLabel::AutoImageLabel(QWidget *parent, const QPixmap &pixmap) : QLabel(parent) {
setMinimumSize(1, 1);
setScaledContents(false);
setPixmap(pixmap);
}
AutoImageLabel::AutoImageLabel(QWidget *parent) : QLabel(parent) {
setScaledContents(false);
}
void AutoImageLabel::resizeEvent(QResizeEvent *event) {
QWidget::resizeEvent(event);
if(!labelPixmap.isNull())
QLabel::setPixmap(scaledPixmap());
qDebug() << "Resized: " << scaledPixmap().width() << ", " << scaledPixmap().height();
}
int AutoImageLabel::widthForHeight(int h) const {
return labelPixmap.isNull() ? width() : (labelPixmap.width() * h) / labelPixmap.height();
}
int AutoImageLabel::heightForWidth(int w) const {
return labelPixmap.isNull() ? height() : (labelPixmap.height() * w) / labelPixmap.width();
}
void AutoImageLabel::setFillOrientation(int orientation) {
this->fillOrientation = orientation;
}
QSize AutoImageLabel::sizeHint() const {
if (fillOrientation == Qt::Horizontal)
return QSize(width(), heightForWidth(width()));
else
return QSize(widthForHeight(height()), height());
}
QPixmap AutoImageLabel::scaledPixmap() const {
return labelPixmap.scaled(sizeHint(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
void AutoImageLabel::setPixmap(const QPixmap &newPix) {
labelPixmap = newPix;
QLabel::setPixmap(scaledPixmap());
}
"other_frames.h":
//
// Created by Riccardo on 18/09/2017.
//
#ifndef APOCRYPHA_OTHER_FRAMES_H
#define APOCRYPHA_OTHER_FRAMES_H
#include <QFrame>
#include <QLabel>
#include <QGridLayout>
#include <QWidget>
#include <QResizeEvent>
#include <QPixmap>
#include <QTimer>
#include "widgets.h"
class ConfirmationFrame : public QFrame {
Q_OBJECT
public:
explicit ConfirmationFrame(QWidget* parent);
ConfirmationFrame(QWidget* parent, const QString& text);
private:
QGridLayout* layout;
AutoImageLabel* imageLabel;
AutoTextLabel* textLabel;
};
#endif //APOCRYPHA_OTHER_FRAMES_H
"other_frames.cpp":
//
// Created by Riccardo on 18/09/2017.
//
#include "other_frames.h"
#include <QDebug>
ConfirmationFrame::ConfirmationFrame(QWidget* parent, const QString &text) : QFrame(parent) {
textLabel = new AutoTextLabel(this, text);
QPixmap pix(":/images/check-tick.png");
imageLabel = new AutoImageLabel(this, pix);
textLabel->setAlignment(Qt::AlignCenter);
imageLabel->setAlignment(Qt::AlignCenter);
textLabel->setWordWrap(true);
// Green Background
setStyleSheet("background-color: rgba(106, 242, 94, 1);");
layout = new QGridLayout();
layout->setSpacing(0);
layout->setContentsMargins(32, 32, 32, 32);
layout->setRowStretch(0, 1);
layout->setRowStretch(1, 1);
layout->addWidget(imageLabel, 0, 1);
layout->addWidget(textLabel, 1, 1);
setLayout(layout);
}
ConfirmationFrame::ConfirmationFrame(QWidget *parent) : ConfirmationFrame(parent, "") {
}
"window_main.h":
#ifndef WINDOW_MAIN_H
#define WINDOW_MAIN_H
#include <QMainWindow>
#include <QMenuBar>
#include <QMenu>
#include <QGridLayout>
#include <QFrame>
#include <QScreen>
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
QFrame *mainFrame;
void center(QScreen* screen);
void autoSetSize(QScreen* screen);
private:
void createMenu();
// Components
QGridLayout *mainLayout;
QMenuBar *menuBar;
QMenu *fileMenu;
};
#endif // WINDOW_MAIN
"window_main.cpp":
#include "window_main.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
mainFrame = new QFrame();
mainLayout = new QGridLayout();
mainLayout->setSpacing(0);
mainLayout->setContentsMargins(0, 0, 0, 0);
createMenu();
mainFrame->setStyleSheet("background-color: red;");
mainFrame->setLayout(mainLayout);
setCentralWidget(mainFrame);
}
void MainWindow::createMenu(){
menuBar = new QMenuBar;
fileMenu = new QMenu(tr("&File"), this);
menuBar->addMenu(fileMenu);
setMenuBar(menuBar);
}
void MainWindow::center(QScreen *screen) {
QSize size = screen->availableSize();
int x = size.width() / 2 - width() / 2;
int y = size.height() / 2 - height() / 2;
move(x, y);
}
void MainWindow::autoSetSize(QScreen *screen) {
QSize screenSize = screen->availableSize();
// TODO Math.round
setMinimumSize(QSize((int)(screenSize.width() / 1.25), (int)(screenSize.height() / 1.25)));
}
"main.cpp":
#include <QApplication>
#include <iostream>
#include <QFile>
#include "quiz/choice.h"
#include "quiz/question.h"
#include "quiz/quizmaker.h"
#include <QSettings>
#include <QStandardPaths>
#include <QDebug>
#include <src/user_interface/other_frames.h>
#include "user_interface/window_main.h"
#include <QScreen>
#include <QFontDatabase>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
// Set Application Parameters
QCoreApplication::setOrganizationName("Riccardo Fagiolo");
QCoreApplication::setOrganizationDomain("kopharex.me");
QCoreApplication::setApplicationName("Apocrypha");
// Set application font
const int id = QFontDatabase::addApplicationFont(":/fonts/montserrat/Montserrat-Regular.otf");
QString family = QFontDatabase::applicationFontFamilies(id).at(0);
QFont font(family);
font.setStyleStrategy(QFont::PreferAntialias);
a.setFont(font);
// App Settings
QSettings settings;
settings.setValue("data_dir", QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
// Create UI
auto* window = new MainWindow();
ConfirmationFrame* cframe = new ConfirmationFrame(window, "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?");
window->mainFrame->layout()->addWidget(cframe);
window->autoSetSize(a.primaryScreen());
//cframe->updateTextLabel();
window->show();
window->center(a.primaryScreen());
// [...] - Nothing related to user interface.
return a.exec();
}
Here is a screenshot of the current MainWindow and ConfirmationFrame to give you an idea of what i'm trying to accomplish:
Window Screenshot
All comments regarding the code are welcome.
Thanks for any help,
Riccardo
Hello I tried to fix the resizing issue with an hack.
Before starting the timer to resize the text, just reduce its font to a 1 pixel font:
void AutoTextLabel::resizeEvent(QResizeEvent *event) {
QWidget::resizeEvent(event);
// set a very small font, then start the timer
QFont updatedFont(font());
updatedFont.setPixelSize(1);
setFont(updatedFont);
// Only fire when 25ms have passed since the last resize.
resizeTimer->start(25);
}
Can the effect be acceptable in your opinion?
I'm trying to use QVTKOpenGLWidget in QTDesigner with vtkGenericOpenGlRenderWindow but I obtain a black window. When I use PCLVizualizer of PointCloud Library (PCL) with vtkGenericOpenGlRenderWindow, everything works fine. I'm trying to create a custom viewer the same way as PCL.
QMainPanel::QMainPanel( HWND hWnd ) :
QWinWidget( hWnd ),
ui(new Ui::QMainPanel)
{
ui->setupUi(this);
rendererTest = vtkSmartPointer<vtkRenderer>::New();
rendererTest->GradientBackgroundOn();
rendererTest->SetBackground(0.27,0.27,0.27);
rendererTest->SetBackground2(0.44,0.44,0.44);
windowTest = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
windowTest->AddRenderer(rendererTest);
interactorTest = vtkSmartPointer<vtkRenderWindowInteractor>::New();
interactorTest->SetRenderWindow( windowTest );
ui->openGLWidget->SetRenderWindow( windowTest ); // QVTKOpenGLWidget *openGLWidget;
}
Thank you
Edit :
Remove "Interactor" solved my problem.
You will need to get the master version of PCL form https://github.com/PointCloudLibrary/pcl. This will give a PCLVisualizer with more constructors. So, you can use a custom widget like this:
VideoQVTKOpenGLWidget.h
#ifndef VIDEOQVKTOPENGLWIDGET_H
#define VIDEOQVKTOPENGLWIDGET_H
#include <QWidget>
#include <QVTKOpenGLWidget.h>
#include <pcl/visualization/pcl_visualizer.h>
using namespace pcl::visualization;
class VideoQVTKOpenGLWidget : public QVTKOpenGLWidget
{
public:
explicit VideoQVTKOpenGLWidget(QWidget *parent = 0);
void populateCloud(pcl::PointCloud<pcl::PointXYZRGBA>::Ptr cloud);
private:
boost::shared_ptr<PCLVisualizer> _viewer;
vtkSmartPointer<vtkGenericOpenGLRenderWindow> _renderWindow;
};
#endif // VIDEOQVKTOPENGLWIDGET_H
VideoQVTKOpenGLWidget.cpp
#include "VideoQVTKOpenGLWidget.h"
#include <vtkPointPicker.h>
#include <vtkGenericOpenGLRenderWindow.h>
VideoQVTKOpenGLWidget::VideoQVTKOpenGLWidget(QWidget *parent)
: QVTKOpenGLWidget(parent)
{
auto renderer = vtkSmartPointer<vtkRenderer>::New();
_renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
_renderWindow->AddRenderer(renderer);
_viewer.reset(new PCLVisualizer(renderer, _renderWindow, "viewer", false));
this->SetRenderWindow(_viewer->getRenderWindow());
this->update();
}
void VideoQVTKOpenGLWidget::populateCloud(pcl::PointCloud <pcl::PointXYZRGBA>::Ptr cloud)
{
if(!_viewer->updatePointCloud(cloud, "cloud")) {
_viewer->addPointCloud(cloud, "cloud");
}
_renderWindow->Render();
}
I am trying to solve a graphics problem using the latest version of Qt. The image below shows what I managed to get so far and I will use it to explain the expected result.
I'm using a main vertical layout and within the biggest widget of the layout there is a horizontal layout with only one child: the square widget. The expected behavior would be of course to have the square widget centered horizontally and taking up the biggest space available. It is not required to use the same layout configuration, but the look of the interface should be the same.
The image above has been obtained by setting a QSizePolicy of minimumExpanding for both vertical and horizontal to the square widget and by forcing it to be square with the following code:
void SquareWidget::resizeEvent(QResizeEvent *event) {
//This is an override to the QWidget method
QSize s = size();
if (s.height()<s.width()) {
resize(s.height(), s.height());
} else {
resize(s.width(), s.width());
}
return;
}
While trying to solve this problem I went through the documentation some of the answers on this website and I couldn't find a clear answer about how to do two tasks.
First problem: how to make the widget square and keep its aspect ratio?
In this question
it is said that the method heightForWidth () doesn't work in newer versions of qt, and after a test it doesn't work for me either. The above override of resizeEvent, on the other hand, causes recursion because there are calls to resize() (and as far as I understand the layout should handle the resizing).
Second problem: how to center the square?
I tried using the layout alignment properties (center horizontally and vertically) but they cause the widget size to be immutable.
Maybe I am not understanding something about how Qt handles the widget placement. Any suggestion or clarification will be greatly appreciated.
You can do it with a QGridLayout.
Please see the attached code.
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class MyWidget final : public QWidget
{
Q_OBJECT
protected:
virtual void resizeEvent(QResizeEvent * event) override;
};
class MainWindow final : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow() = default;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include <QGridLayout>
#include <QLabel>
#include <QResizeEvent>
#include <QSpacerItem>
void MyWidget::resizeEvent(QResizeEvent * event)
{
event->accept();
const QSize current_size = size();
const int min = std::min(current_size.width(), current_size.height());
resize(min, min);
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
auto main_widget = new QWidget;
auto header = new QLabel("Hello World");
auto center_widget = new MyWidget;
auto footer = new QLabel("Good bye World");
auto spacer_left = new QSpacerItem(10, 10, QSizePolicy::Expanding);
auto spacer_right = new QSpacerItem(10, 10, QSizePolicy::Expanding);
auto grid_layout = new QGridLayout(main_widget);
auto center_palette = center_widget->palette();
center_palette.setColor(QPalette::Background, Qt::blue);
center_widget->setAutoFillBackground(true);
center_widget->setPalette(center_palette);
grid_layout->addWidget(header, 0, 1);
grid_layout->addItem(spacer_left, 1, 0);
grid_layout->addWidget(center_widget, 1, 1);
grid_layout->addItem(spacer_right, 1, 2);
grid_layout->addWidget(footer, 2, 1);
header->setAlignment(Qt::AlignCenter);
footer->setAlignment(Qt::AlignCenter);
setCentralWidget(main_widget);
}
Please see the result here
Rather than trying to get the widget to keep itself square and centred it might be simpler to reparent it and put the required logic in the parent widget type.
So, something like...
class keep_child_square_and_centred: public QWidget {
using super = QWidget;
public:
explicit keep_child_square_and_centred (QWidget *parent = nullptr)
: super(parent)
, m_widget(nullptr)
{}
virtual void set_widget (QWidget *widget)
{
if ((m_widget = widget))
m_widget->setParent(this);
}
virtual QSize sizeHint () const override
{
return(m_widget ? m_widget->sizeHint() : super::sizeHint());
}
protected:
virtual void resizeEvent (QResizeEvent *event) override
{
super::resizeEvent(event);
fixup();
}
private:
void fixup ()
{
if (m_widget) {
QRect r(QPoint(), QSize(height(), height()));
r.moveCenter(rect().center());
m_widget->setGeometry(r);
}
}
QWidget *m_widget;
};
Then use as...
keep_child_square_and_centred w;
SquareWidget sq;
w.set_widget(&sq);
You may still need to play around with a few settings if the parent is in a layout though.
I have modified the osgViewerQt example in order to load a point cloud and visualize it in a Qt application. As you can see in the attached image, the cloud point cloud is shown but there is an extra border in the window (see the arrows).
I spent all the weekend trying to figure how to "expand" the window in order to remove that border, but it keeps showing.
Do you know what can I do to remove it? I'll post the code for the modified osgViewerQt and the piece of code where I use it.
viewer_widget.h
#ifndef VIEWER_WIDGET_H
#define VIEWER_WIDGET_H
#include "osgViewer/CompositeViewer"
#include <QTimer>
#include <QWidget>
class QGridLayout;
class QWidget;
class ViewerWidget : public QWidget, public osgViewer::CompositeViewer {
private:
std::string cloud_file;
std::string cloud_filepath;
QTimer timer_;
QWidget* widget;
QGridLayout* grid;
osg::ref_ptr<osgViewer::View> view;
private:
ViewerWidget(const ViewerWidget& V);
ViewerWidget& operator=(const ViewerWidget& V);
private:
QWidget* AddViewWidget(osg::Camera* camera,osg::Node* scene);
osg::Camera* CreateCamera(int x,int y,int w,int h,const std::string& name="",
bool windowDecoration=false
);
osg::Node* ReadOctree(const std::string& file);
public:
ViewerWidget(const std::string& filename,const std::string& filepath,bool color,
osgViewer::ViewerBase::ThreadingModel threadingModel
= osgViewer::CompositeViewer::ThreadPerCamera
);
virtual ~ViewerWidget(void){}
void AddCloud(void);
void StartFrameTimer(int msec=10) { timer_.start(msec); }
virtual void paintEvent( QPaintEvent* event ) { frame(); }
};
#endif // VIEWER_WIDGET_H
osg_viewer.cpp
#include "viewer_widget.h"
#include "osgDB/ReadFile"
#include "osgGA/TrackballManipulator"
#include "osgQt/GraphicsWindowQt"
#include "osgViewer/ViewerEventHandlers"
#include <QGridLayout>
#include <QDebug>
ViewerWidget::ViewerWidget(const std::string &filename,const std::string &filepath,
bool color, osgViewer::ViewerBase::ThreadingModel threadingModel
) :
QWidget(),
cloud_file( filename ),
cloud_filepath( filepath )
{
// this->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
connect( &(this->timer_), SIGNAL(timeout()), this, SLOT(update()) );
}
QWidget* ViewerWidget::AddViewWidget(osg::Camera *camera,osg::Node *scene) {
view = new osgViewer::View;
view->setCamera( camera );
view->setSceneData( scene );
osg::Stats* stats = this->getViewerStats();
if(stats) stats->report(std::cout);
addView( view );
view->addEventHandler( new osgViewer::StatsHandler );
view->setCameraManipulator( new osgGA::TrackballManipulator );
osgQt::GraphicsWindowQt* gw = dynamic_cast<osgQt::GraphicsWindowQt*>(
camera->getGraphicsContext()
);
return gw ? gw->getGLWidget() : 0;
}
osg::Camera* ViewerWidget::CreateCamera(int x,int y,int w,int h,const std::string &name,
bool windowDecoration
) {
osg::DisplaySettings* ds = osg::DisplaySettings::instance().get();
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
traits->windowName = name;
traits->windowDecoration = windowDecoration;
traits->x = x;
traits->y = y;
qDebug() << "w:" << w << " h:" << h;
traits->width = w;
traits->height = h;
traits->doubleBuffer = true;
traits->alpha = ds->getMinimumNumAlphaBits();
traits->stencil = ds->getMinimumNumStencilBits();
traits->sampleBuffers = ds->getMultiSamples();
traits->samples = ds->getNumMultiSamples();
osg::ref_ptr<osg::Camera> camera = new osg::Camera;
camera->setGraphicsContext( new osgQt::GraphicsWindowQt(traits.get()) );
camera->setClearColor( osg::Vec4(0,0,0,1) );
camera->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) );
camera->setProjectionMatrixAsPerspective(
30.0f, static_cast<double>(traits->width)/static_cast<double>(traits->height), 1.0f, 10000.0f );
return camera.release();
}
osg::Node* ViewerWidget::ReadOctree(const std::string &file) {
osg::Group* group = new osg::Group;
group->addChild( osgDB::readNodeFile(file, options) );
return group;
}
void ViewerWidget::AddCloud() {
std::cout << "Loading cloud from file:" << cloud_file.c_str() << "\n";
QWidget* widget = AddViewWidget(
CreateCamera(0,0,100,100,"cam1",true),
ReadOctree(cloud_file)
);
grid = new QGridLayout;
grid->addWidget( widget, 0, 0 );
this->setLayout( grid );
}
Now, in where this widget is used (simplified a bit to show only the relevant parts):
cloud.h
#ifndef CLOUD_H
class Cloud: public QObject {
Q_OBJECT
private:
osg::ref_ptr<ViewerWidget> osg_widget;
QDockWidget* dock;
/// MORE ATTRIBUTES
public:
Cloud(){
/// ...
dock = new QDockWidget;
osg_widget = new ViewerWidget( getFileName(), getFilePath(), has_color);
dockWidget->setAllowedAreas(Qt::RightDockWidgetArea);
dockWidget->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
dockWidget->addWidget(osg_widget);
}
/// More methods, cloud manipulators, etc.
};
#endif
When specifying the size policy, I have also tried with Minimum, MinimumExpanding and
Ignored, but with the same effect. I tried to specify the size policy directly inside the ViewerWidget (as it inherits from QWidget) and to specify it its widget attribute, too, but with no success.
You're using a grid layout to insert the view widget in your target window, if I understand correctly:
grid = new QGridLayout;
grid->addWidget( widget, 0, 0 );
this->setLayout( grid );
Layouts usually insert padding around their elements (called margin in the Qt docs). You can tune that using QLayout::setContentsMargin(), so here something in the spirit of this
grid->setContentsMargins(0,0,0,0);
should do the trick.