I'm new to Qt and GStreamer, but I need to create a simple player for a QuickTime/H.264 video file into a Qt 5.15.2 application (running on Linux UbuntuĀ 20.04 (Focal Fossa)).
I managed to play a standard videotestsrc (bouncing ball pattern) inside my application, and this is the code (main.cpp):
#include "mainwindow.h"
#include <QApplication>
#include <QQuickView>
#include <QWidget>
#include <QQuickItem>
#include <gst/gst.h>
int main(int argc, char *argv[])
{
GstElement* mPipeline = nullptr;
GstElement* mSource = nullptr;
GstElement* mGLUpload = nullptr;
GstElement* mSink = nullptr;
QQuickView* mView = nullptr;
QWidget* mWidget = nullptr;
QQuickItem* mItem = nullptr;
gst_init(argc, argv);
QApplication app(argc, argv);
MainWindow* window = new MainWindow;
mPipeline = gst_pipeline_new(NULL);
mSource = gst_element_factory_make("videotestsrc", NULL);
mGLUpload = gst_element_factory_make("glupload", NULL);
mSink = gst_element_factory_make("qmlglsink", NULL);
gst_bin_add_many(GST_BIN (mPipeline), mSource, mGLUpload, mSink, NULL);
gst_element_link_many(mSource, mGLUpload, mSink, NULL);
g_object_set(mSource, "pattern", 18, NULL);
mView = new QQuickView;
mView->scheduleRenderJob(new SetPlaying (mPipeline),
QQuickView::BeforeSynchronizingStage);
mView->setSource(QUrl(QStringLiteral("qrc:/video.qml")));
mWidget = QWidget::createWindowContainer(mView, parent);
mItem = mView->findChild<QQuickItem*>("videoItem");
window->setCentralWidget(mWidget);
window->show();
ret = app.exec();
g_object_set(mSink, "widget", mItem, NULL);
gst_deinit();
}
SetPlaying class...
#include <QRunnable>
#include <gst/gst.h>
class SetPlaying : public QRunnable
{
public:
SetPlaying(GstElement *pipeline) {
this->pipeline_ = pipeline ? static_cast<GstElement *> (gst_object_ref (pipeline)) : NULL;
}
~SetPlaying() {
if (this->pipeline_)
gst_object_unref (this->pipeline_);
}
void run () {
if (this->pipeline_)
gst_element_set_state (this->pipeline_, GST_STATE_PLAYING);
}
private:
GstElement * pipeline_;
};
The MainWindow code should not be relevant for the issue management (it's a standard empty window).
This is the source code of the only .qml item that's needed to provide an acceptable widget surface to qmlglsink:
import QtQuick 2.15
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.3
import QtQuick.Dialogs 1.2
import QtQuick.Window 2.1
import org.freedesktop.gstreamer.GLVideoItem 1.0
Item {
anchors.fill: parent
GstGLVideoItem {
id: video
objectName: "videoItem"
anchors.centerIn: parent
width: parent.width
height: parent.height
}
}
Now since the actual pipeline to plays the file is quite long and complex to manage the # code I opted to use a gst_parse_launch() approach.
To proceed step by step, I tried to use such a method to create a videotestsrc pipeline, i.e.:
mPipeline = gst_parse_launch( "videotestsrc ! glupload ! qmlglsink", NULL);
mSink = gst_bin_get_by_name(GST_BIN(mPipeline), "sink");
mSource = gst_bin_get_by_name(GST_BIN(mPipeline), "source");
If I run the code this is the result:
(videotest:14930): GLib-GObject-CRITICAL **: 16:33:08.868: g_object_set: assertion 'G_IS_OBJECT (object)' failed
(videotest:14930): GLib-GObject-CRITICAL **: 16:33:09.342: g_object_set: assertion 'G_IS_OBJECT (object)' failed
Of course, the application window displays nothing.
You should give the elements a name property. They will default to ones, but they include a numerical value and are incremented whenever you rebuild the pipeline. So it is better to not rely on those.
To make your existing code work, try this:
mPipeline = gst_parse_launch( "videotestsrc name=source ! glupload ! qmlglsink name=sink", NULL);
Related
Here is my code :
/// Get the Gtk2+ window ID of wxPanel pnlview for GStreamer output
GtkWidget* video_window = pnlView->GetHandle();
// Without this line, GStreamer will create its own new window for the video stream.
gtk_widget_realize(video_window);
GdkWindow *videoareaXwindow = gtk_widget_get_window(video_window);
data.xid = GDK_WINDOW_XID(videoareaXwindow) // Get xid for setting overlay later
were pnlView is define as wxPanel* pnlView;
But the consol give me this error : Impossible to convert 'WXWidget' in 'GtkWidget * at the line where I initialize video_window
Can someone has any idea how to fix it ?
I just want to add my gstreamer window in my wxWindow
Thank you
I've never used gstreamer, but I do sometimes use libvlc which is probably very similar. When using libvlc to render into a wxWindow, I need to wait until the wxWindow is fully created before I can set vlc to use it. This is done by adding an event handler for the window creation event.
The process of declaring and binding the event handler looks like this:
class MainWindow : public wxFrame
{
...
// Event handlers
void OnRendererWinCreated(wxWindowCreateEvent& event);
}
MainWindow::MainWindow(...)
{
...
#ifdef __WXGTK__
// On GTK+, we have to wait until the window is actually created before we
// can tell VLC to use it for output. So wait for the window create event.
pnlView->Bind(wxEVT_CREATE, &MainWindow::OnRendererWinCreated, this);
#elif defined(__WXMSW__)
m_player.setHwnd(pnlView->GetHandle());
#endif
...
}
For libvlc, my window creation event handler looks like this:
void MainWindow::OnRendererWinCreated(wxWindowCreateEvent& event)
{
#ifdef __WXGTK__
m_player.setXwindow(
gdk_x11_window_get_xid(gtk_widget_get_window(pnlView->GetHandle()))
);
pnlView->Unbind(wxEVT_CREATE,&MainWindow::OnRendererWinCreated,this);
#endif
}
Based on the code you posted, I think the body of an event handler that will work for you should look something like this:
void MainWindow::OnRendererWinCreated(wxWindowCreateEvent& event)
{
#ifdef __WXGTK__
/// Get the Gtk2+ window ID of wxPanel pnlview for GStreamer output
GtkWidget* video_window = pnlView->GetHandle();
GdkWindow *videoareaXwindow = gtk_widget_get_window(video_window);
data.xid = GDK_WINDOW_XID(videoareaXwindow) // Get xid for setting overlay later
pnlView->Unbind(wxEVT_CREATE,&MainWindow::OnRendererWinCreated,this);
#endif
}
Edit:
Here's a simple example of using GStreamer to draw onto a wxWindow on GTK. This shows how to use the wxEVT_CREATE to get the XID for a window and how to use GStreamer's bus sync handler callback to pass that XID to GStreamer at the correct time.
This is basically a mashup of the 2nd tutorial and the code snippet from the GstVideoOverlay page adjusted for wxWidgets. Since this is based on the 2nd tutorial, it just shows a test pattern. The source variable can be changed to show other videos.
Obviously this assumes GTK is using X11. Some adjustments would be needed if Wayland is used instead, but I don't have a running distro that uses Wayland to test on, so I don't know what changes are needed there.
#include "wx/wx.h"
#ifdef __WXGTK__
#include <gdk/gdkx.h>
#include <gtk/gtk.h>
#endif
#include <gst/gst.h>
#include <gst/video/videooverlay.h>
class MainWindow : public wxFrame
{
public:
MainWindow(const wxString& title);
~MainWindow();
private:
// Event handlers
void OnRendererWinCreated(wxWindowCreateEvent&);
void OnPlay(wxCommandEvent&);
void OnStop(wxCommandEvent&);
// Helper function
void LoadVideo();
void PlayHelper();
// wx controls
wxWindow* m_renderWindow;
wxButton* m_playButton;
wxButton* m_stopButton;
// GStreamer data
GstElement* m_pipeline;
guintptr m_xid;
};
MainWindow::MainWindow(const wxString& title) : wxFrame(NULL, wxID_ANY, title)
{
// Create the UI widgets.
wxPanel* bg = new wxPanel(this,wxID_ANY);
m_renderWindow = new wxWindow(bg,wxID_ANY);
m_playButton = new wxButton(bg,wxID_ANY,"Play");
m_stopButton = new wxButton(bg,wxID_ANY,"Stop");
m_renderWindow->SetBackgroundColour(*wxBLACK);
m_playButton->Enable(true);
m_stopButton->Enable(false);
// Layout the UI.
wxBoxSizer* szr1 = new wxBoxSizer(wxVERTICAL);
wxBoxSizer* szr2 = new wxBoxSizer(wxHORIZONTAL);
szr2->Add(m_playButton, wxSizerFlags(0).Border(wxLEFT|wxRIGHT|wxBOTTOM));
szr2->Add(m_stopButton, wxSizerFlags(0).Border(wxRIGHT|wxBOTTOM));
szr1->Add(m_renderWindow, wxSizerFlags(1).Expand().Border(wxBOTTOM));
szr1->Add(szr2, wxSizerFlags(0));
bg->SetSizer(szr1);
Layout();
// Set up the event handlers.
#ifdef __WXGTK__
m_renderWindow->Bind(wxEVT_CREATE, &MainWindow::OnRendererWinCreated, this);
m_playButton->Enable(false);
#endif
m_playButton->Bind(wxEVT_BUTTON, &MainWindow::OnPlay, this);
m_stopButton->Bind(wxEVT_BUTTON, &MainWindow::OnStop, this);
// Initialize GStreamer.
m_xid = 0;
m_pipeline = NULL;
gst_init(NULL, NULL);
}
MainWindow::~MainWindow()
{
if ( m_pipeline )
{
gst_element_set_state(m_pipeline, GST_STATE_NULL);
gst_object_unref(m_pipeline);
}
}
void MainWindow::OnRendererWinCreated(wxWindowCreateEvent&)
{
#ifdef __WXGTK__
// This event is no longer needed.
m_renderWindow->Unbind(wxEVT_CREATE,&MainWindow::OnRendererWinCreated,this);
// Get the XID for this window.
m_xid = GDK_WINDOW_XID(gtk_widget_get_window(m_renderWindow->GetHandle()));
// We can now load and play the video, so enable the play button.
m_playButton->Enable(true);
#endif
}
void MainWindow::OnPlay(wxCommandEvent&)
{
if ( m_pipeline )
{
PlayHelper();
}
else
{
LoadVideo();
}
}
void MainWindow::OnStop(wxCommandEvent&)
{
if ( m_pipeline )
{
GstStateChangeReturn ret =
gst_element_set_state(m_pipeline, GST_STATE_PAUSED);
if ( ret == GST_STATE_CHANGE_FAILURE )
{
wxLogWarning("Unable to set the pipeline to the paused state.");
gst_object_unref(m_pipeline);
m_pipeline = NULL;
m_playButton->Enable(true);
m_stopButton->Enable(false);
}
else
{
m_playButton->Enable(true);
m_stopButton->Enable(false);
}
}
}
void MainWindow::LoadVideo()
{
// Create the elements
GstElement *source = gst_element_factory_make("videotestsrc", "source");
#ifdef __WXGTK__
GstElement *sink = gst_element_factory_make("xvimagesink", "sink");
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(sink), m_xid);
#elif defined __WXMSW__
GstElement *sink = gst_element_factory_make("d3dvideosink", "sink");
WXWidget hwnd = m_renderWindow->GetHandle();
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(sink),
reinterpret_cast<guintptr>(hwnd));
#endif
//Create the empty pipeline
m_pipeline = gst_pipeline_new ("test-pipeline");
if ( !m_pipeline || !source || !sink )
{
wxLogError("Not all elements could be created.");
return;
}
// Build the pipeline
gst_bin_add_many(GST_BIN(m_pipeline), source, sink, NULL);
if ( gst_element_link(source, sink) != TRUE )
{
wxLogWarning("Elements could not be linked.");
gst_object_unref(m_pipeline);
m_pipeline = NULL;
return;
}
// Modify the source's properties
g_object_set(source, "pattern", 0, NULL);
PlayHelper();
}
void MainWindow::PlayHelper()
{
GstStateChangeReturn ret =
gst_element_set_state(m_pipeline, GST_STATE_PLAYING);
if ( ret == GST_STATE_CHANGE_FAILURE )
{
wxLogWarning("Unable to set the pipeline to the playing state.");
gst_object_unref(m_pipeline);
m_pipeline = NULL;
m_playButton->Enable(true);
m_stopButton->Enable(false);
}
else
{
m_playButton->Enable(false);
m_stopButton->Enable(true);
}
}
class MyApp : public wxApp
{
public:
bool OnInit() override
{
MainWindow* mainWindow = new MainWindow("wxWidgets GStreamer demo");
mainWindow->Show();
return true;
}
};
wxIMPLEMENT_APP(MyApp);
On mint it looks like this:
On windows it looks like this:
I am trying to create a qml object dynamically in c++ using the object of c++ class. Below is the minimal code for my approach. Upon execution of this code and after clicking, the application is crashing(see the comment in main.qml).
I have pasted the code below and It can be downloaded here.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "scene.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
scene sc(engine);
QQmlContext* context = engine.rootContext();
context->setContextProperty("sc", &sc);
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
scene.h
#ifndef SCENE_H
#define SCENE_H
#include <QObject>
#include <QQmlApplicationEngine>
#include <QQmlComponent>
class scene : public QObject
{
Q_OBJECT
public:
explicit scene(QQmlApplicationEngine& engine, QObject *parent = nullptr);
QQmlApplicationEngine& engine;
public slots:
void create_rect_object();
};
#endif // SCENE_H
scene.cpp
#include "scene.h"
scene::scene(QQmlApplicationEngine &engine, QObject *parent) : engine(this->engine),QObject(parent)
{
}
void scene::create_rect_object()
{
QQmlComponent component(&engine, QUrl::fromLocalFile("myrect.qml"));
QObject *object = component.create();
object->setProperty("width", 200);
object->setProperty("height", 150);
object->setProperty("color", "blue");
}
main.qml
import QtQuick 2.11
import QtQuick.Window 2.11
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle{
anchors.fill: parent
color: "red"
MouseArea{
anchors.fill: parent
onClicked: {
console.log("Before click");
sc.create_rect_object(); // application is crashing here
console.log("after click");
}
}
}
}
myrect.qml
import QtQuick 2.0
Rectangle {
id:id_rec
width: 100
height: 100
color: "green"
x:0
y:0
}
Update
The object to be created is not the child of the root of main window but child of one of the item inside the chain of children items of root of mainwindow. The pseudo structure looks like below.
main.qml
Window {
customitem1{
id:id_ci1
}
customitem2{
id:id_ci1
}
}
customitem1.qml
Item {
customitem3{
id:id_ci3
}
customitem3{
id:id_ci4
}
}
[UPDATED]
You have two errors for crashing and one for not showing rectangles
1.Your scene's Constructor member initializer list is falsy which causes the app crash
(TIP : use different naming for members of the class by prefixing them with m_ e.g: m_engine for READABILITY and not get confused)
//Correct WAY
class Something
{
private:
int m_value1;
double m_value2;
char m_value3;
public:
//################# YOUR CASE ###############################
Something(int number) : m_value1(number), m_value2(2.2), m_value3('c') // directly initialize our member variables
{
// No need for assignment here
}
//#############################################################
Something() : m_value1(1), m_value2(2.2), m_value3('c') // directly initialize our member variables
{
// No need for assignment here
}
void print()
{
std::cout << "Something(" << m_value1 << ", " << m_value2 << ", " << m_value3 << ")\n";
}
}
and it should be like this :
scene::scene(QQmlApplicationEngine &engine, QObject *parent) : engine(engine),QObject(parent)
instead of
scene::scene(QQmlApplicationEngine &engine, QObject *parent) : engine(this->engine),QObject(parent)
2.The url of myrect.qml which you get from local file that isn't found at runtime caused the app crash aslo and the one of remedies is to load it from your qrc file
QQmlComponent component(&engine, QUrl("qrc:/myrect.qml"));
3.And you'll notice after clicking you got no rectangles that's because the rectangles getting created doesn't have a parent and by changing your create_rect_object()(In this example the parent is the invisible root of our window contentItem) you'll get some rectangles :)
//A QQuickWindow always has a single invisible root item containing all of its content.
//To add items to this window, reparent the items to the contentItem or to an existing item in the scene.
//http://doc.qt.io/qt-5/qquickwindow.html#contentItem-prop
void scene::create_rect_object()
{
QQmlComponent component(&engine, QUrl("qrc:/myrect.qml"));
QObject *object = component.create();
QQuickItem *item = qobject_cast<QQuickItem*>(object);
// Set the parent of our created qml rect
item->setParentItem((QQuickItem*)((QQuickWindow *) engine.rootObjects()[0])->contentItem());
//Set some random position and color
item->setProperty("color", QColor::fromRgb(QRandomGenerator::global()->generate()));
item->setX(20+qFloor(QRandomGenerator::global()->generateDouble()*20));
item->setY(20+qFloor(QRandomGenerator::global()->generateDouble()*20));
}
Finding QML Objects from C++
For finding objects and using them as parentItem, you have to set the objectName of your qml object
Rectangle {
...
objectName : "rect_1"
...
}
and in C++
QObject* obj = dynamic_cast<QObject*>(engine.rootObjects()[0]).findChild("rect_1");
I have a bit of a strange error being caused by a seemingly simple problem.
In my source code I am attempting to use QQuickPaintedItem to render just the overall look of a QWidget derived class (QPushButton), and then painting it on to the QQuickPaintedItem
In doing so I ran into this error:
QQmlComponent: Created graphical object was not placed in the graphics
scene.
Followed by:
The program has unexpectedly finished.
Here's the context I am trying to create my special QQuickPaintedItem in along with the source code for it:
main.qml
import QtQuick 2.9
import com.particletool 1.0
import QtQuick.Particles 2.0
import QtQuick.Window 2.3
import QtQuick.Controls 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Controls 1.4 as QQ1
Item {
id: root
width: Screen.width
height: Screen.height
WidgetInterface {
id: w
}
}
widgetInterface.h
#ifndef WIDGETINTERFACE_H
#define WIDGETINTERFACE_H
#include <QObject>
#include <QQuickItem>
#include <QQuickPaintedItem>
#include <QWidget>
#include <QPushButton>
#include <QtQuick>
class WidgetInterface : public QQuickPaintedItem
{
public:
WidgetInterface(QQuickItem *parent = nullptr, QWidget* renderWidget = nullptr);
QWidget* m_widget;
QPushButton* m_button;
protected:
void paint(QPainter* painter);
public slots:
void morphIntoButton(QString txt);
};
#endif // WIDGETINTERFACE_H
widgetinterface.cpp
#include "widgetinterface.h"
#include <QQuickPaintedItem>
#include <QObject>
#include <QPainter>
#include <QWidget>
#include <QPushButton>
WidgetInterface::WidgetInterface(QQuickItem *parent, QWidget* renderWidget) : QQuickPaintedItem(parent), m_widget(renderWidget)
{
morphIntoButton("test");
QQmlEngine::setObjectOwnership(m_button, QQmlEngine::JavaScriptOwnership);
}
void WidgetInterface::paint(QPainter *painter)
{
if (m_widget != nullptr) {
painter->end();
m_button->render(painter);
} else {
}
}
void WidgetInterface::morphIntoButton(QString txt)
{
m_widget->setGeometry(0,0, this->width(), this->height());
m_button = new QPushButton(m_widget);
m_button->setGeometry(m_widget->geometry());
m_button->setText(txt);
this->update(this->boundingRect().toRect());
}
main.cpp
#include <QtGui/QGuiApplication>
#include <QApplication>
#include <QtQml/QQmlApplicationEngine>
#include "src/interfaces/widgetinterface.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterType<WidgetInterface>("com.particletool", 1, 0, "WidgetInterface");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
The expected result is a button being drawn on to the QML scene graph
Does anyone know how I can achieve this using some method? ( I know that its not supported by Qt, but I am trying to implement a way to make it happen so dont both with the "You can't" answers because there is a way I'm sure.
The expected result is a button being drawn on to the QML scene graph
class WidgetInterface : public QQuickPaintedItem
{
public:
WidgetInterface(QQuickItem *parent = Q_NULLPTR) :
QQuickPaintedItem(parent)
{
mButton = new QPushButton("TEST", Q_NULLPTR);
auto resize = [=](){
mButton->setGeometry(QRect(QPoint(), boundingRect().size().toSize()));
QPixmap p(mButton->size());
mButton->render(&p);
if(!p.isNull())
mButtonPix = p;
update();
};
connect(this, &QQuickPaintedItem::widthChanged, this, resize, Qt::QueuedConnection);
connect(this, &QQuickPaintedItem::heightChanged, this, resize, Qt::QueuedConnection);
}
~WidgetInterface() {
mButton->deleteLater();
}
protected:
void paint(QPainter* painter){
painter->drawPixmap(0, 0, mButtonPix);
}
private:
QPushButton* mButton;
QPixmap mButtonPix;
};
I have spent the last few days trying to embed an SDL2 display into wxWidgets but failing miserably. OK, here is what I have so far...
First a little detail:
wxWidgets version: 3.1 | SDL version 2.0.4 | OS: Fedora 23 | g++ 5.3.1
I have searched Google but cannot find much up-to-date information. What I have found suggests that I should use SDL_CreateWindowFrom() and then link it to a wxPanel or wxStaticBitmap.
I have written test code which implements a simple wxFrame and initialises SDL2, the code compiles fine but hangs when run.I am new to SDL2 and so would be grateful if somebody would take a look at my code and see what I am doing wrong. I'm obviously misunderstanding something here so any help would be appreciated.
The following code is a simplified block which highlights my problem.
main.h
#include <wx/wx.h>
#include <SDL2/SDL.h>
class TestApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
wxIMPLEMENT_APP_NO_MAIN(TestApp);
// Initialise SDL before WX-Widgets and GTK3
int main(int argc, char** argv)
{
// Initialise SDL and Error Check
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
std::cerr << "SDL Initialisation Error\n\n";
}
return wxEntry(argc, argv);
}
// WX-WIDGETS ENTRY POINT: Handover control to WX-Widgets
bool TestApp::OnInit()
{
UI_Frame *frame = new UI_Frame("SDL Playing with wxWidgets");
frame->Show(true);
return true;
}
TestApp.h
class UI_Frame : public wxFrame
{
public:
UI_Frame(const wxString& title);
private:
wxDECLARE_EVENT_TABLE();
};
And finally, the method which handles SDL and wxFrame
TestApp.cpp
wxBEGIN_EVENT_TABLE(UI_Frame, wxFrame)
wxEND_EVENT_TABLE()
UI_Frame::UI_Frame(const wxString& title) : wxFrame(NULL, wxID_ANY, title)
{
this->SetSize(1024, 768);
wxBoxSizer* topSizer;
topSizer = new wxBoxSizer(wxVERTICAL);
wxPanel *panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
topSizer->Add(panel, 1, wxEXPAND | wxALL, 5);
/*******************************************************
*
* SDL Specific Stuff
*
******************************************************/
SDL_Window *sdl_window = nullptr;
SDL_Renderer *renderer = nullptr;
// SDL: Create Embedded Window in wxWidgets
sdl_window = SDL_CreateWindowFrom((void *) panel->GetHandle());
if (sdl_window == NULL) {
std::cerr << "SDL NULL Pointer ERROR";
return;
}
// *** This line causes the application to hang ***
renderer = SDL_CreateRenderer(sdl_window, -1, SDL_RENDERER_ACCELERATED);
if (renderer == NULL) {
fprintf(stderr, "SDL: failed to create renderer: %s\n", SDL_GetError());
}
}
I'm trying to make an app where you can draw with your finger on a canvas.
To achieve this, I'm subclassing QWidget as MFCanvas, registered the class in QML with
qmlRegisterType<>(), implementing the virtual paintEvent(); function, and
drawing on it with a QPainter inside the paintEvent(); function.
The Problem:
Upon construction, the QPainter throws this warning:
QWidget::paintEngine: Should no longer be called
Then, serveral other related warnings are thrown:
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setPen: Painter not active
No wonder: the QPainter didn't draw anything...
Also, am i supposed to call paintEvent(); by myself?
Or should it be called every frame by QWidget, and i somehow messed it up?
I searched the web, but all posts i found had either no answer to them, or they where
using something else than QWidget.
My Code:
mfcanvas.cpp:
#include "mfcanvas.h"
#include <QDebug>
#include <QPainter>
#include <QVector2D>
#include <QList>
MFCanvas::MFCanvas(QWidget *parent) : QWidget(parent)
{
paths = new QList<QList<QVector2D>*>();
current = NULL;
QWidget::resize(100, 100);
}
MFCanvas::~MFCanvas()
{
delete paths;
}
void MFCanvas::paintEvent(QPaintEvent *)
{
if(current!=NULL){
if(current->length() > 1){
QPainter painter(this);
painter.setPen(Qt::black);
for(int i = 1; i < current->length(); i++){
painter.drawLine(current->at(i-1).x(), current->at(i-1).y(), current->at(i).x(), current->at(i).y());
}
}
}
}
void MFCanvas::pressed(float x, float y)
{
if(current==NULL){
qDebug() << "null:"<<current;
current = new QList<QVector2D>();
current->append(QVector2D(x, y));
}else{
qDebug() << "current:"<<current;
}
paintEvent(NULL);
}
void MFCanvas::update(float x, float y)
{
current->append(QVector2D(x, y));
}
void MFCanvas::resize(int w, int h)
{
QWidget::resize(w, h);
}
main.cpp:
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include <QSurfaceFormat>
#include "creator.h"
#include "mfcanvas.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
qmlRegisterType<MFCanvas>("com.cpp.mfcanvas", 1, 0, "MFCanvas");
QQmlApplicationEngine engine;
QQmlComponent *component = new QQmlComponent(&engine);
QObject::connect(&engine, SIGNAL(quit()), QCoreApplication::instance(), SLOT(quit()));
Creator creator(component);
QObject::connect(component, SIGNAL(statusChanged(QQmlComponent::Status)), &creator, SLOT(create(QQmlComponent::Status)));
component->loadUrl(QUrl("qrc:///main.qml"));
int rv;
rv = app.exec();
delete component;
return rv;
}
creator.cpp:
#include "creator.h"
#include <QQuickWindow>
#include <QDebug>
Creator::Creator(QQmlComponent *component)
{
this->component = component;
}
void Creator::create(QQmlComponent::Status status)
{
if(status == QQmlComponent::Ready){
QObject *topLevel = component->create();
QQuickWindow::setDefaultAlphaBuffer(true);
QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);
QSurfaceFormat surfaceFormat = window->requestedFormat();
window->setFormat(surfaceFormat);
window->show();
}
}
main.qml: (the important part)
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.2
import QtQuick.Layouts 1.1
import QtQuick.Window 2.0
import com.cpp.mfcanvas 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("MFCanvas")
onSceneGraphInitialized: {
drawMenu.visible = true;
lineWidth.visible = true;
colorMenu.visible = true;
drawMenu.visible = false;
lineWidth.visible = false;
colorMenu.visible = false;
}
Rectangle {
id: main
anchors.fill: parent
property real toolsH: 15
property real iconW: 25
property real menuH: 8
property real menuW: 16
property real dpi: (Screen.logicalPixelDensity == undefined ? 6 : Screen.logicalPixelDensity) * 1.5
property color choosenColor: Qt.hsla(hue.value, saturation.value, luminance.value, 1)
Text {
anchors.centerIn: parent
font.pointSize: 60
text: "MFCanvas"
}
MFCanvas {
id: canvas
Component.onCompleted: {
canvas.resize(main.width, main.height);
}
}
//...
}
}
Tell me if you need any additional information.
Thank you in advance! =)
This is nicely explained here:
https://forum.qt.io/topic/64693
In short: do not try to paint from the input event handler directly,
but overload the paintEvent method in your widget instead and create the
QPainter there. Use the input event exclusively to modify the internal
data model and use QPainter in paintEvent to display it, on the output path.
In your mfcanvas.cpp, void MFCanvas::pressed(float x, float y) function, the line
paintEvent(NULL);
seems to be disturbing. Tried it in a similar code - I get the same error.
Proposed solution: using this->repaint() or this->update() instead of paintEvent(NULL) to repaint a widget seems to be more appropriate.
Possible explanation: looks like paintEvent() shouldn't be called this straightforward (like paintEvent() is called when paint() function is called). As far as I understand from the QPainter doc, the QPainter works together with the QPaintDevice and the QPaintEngine, these three form the basis for painting. The error QWidget::paintEngine: Should no longer be called puts it quite straight. The lines
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setPen: Painter not active
probably indicate that there's no QPaintEngine provided by this painter's QPaintDevice (like QPaintDevice::paintEngine). One can assume that this QPaintEngine is generated or otherwise called to existence by the paint device itself, for example, when the paint() function is called on a widget.
I have found a simple solution myself:
Instead of deriving from QWidget, derive from QQuickPaintedItem. QQuickPaintedItem is a class that was made exactly for what i need: Painting on a QML-Element using a QPainter. Here is the Code (Narrowed down to the essential part):
mfcanvas.h:
class MFCanvas : public QQuickPaintedItem
{
Q_OBJECT
public:
explicit MFCanvas(QQuickItem *parent = 0);
~MFCanvas();
protected:
void paint(QPainter *painter);
mfcanvas.cpp:
void MFCanvas::paint(QPainter *painter)
{
painter->translate(-translation.x(), -translation.y());
//...
}
As you can see, a simple paint() function is provided which hands over a pointer to a QPainter, ready to use. =)