Qt Button Group over OpenGLWidget - qt

I am designing a user interface with Qt Creator. I have a openGLwidget which covers all the form and I want to place some buttons aligned to bottom and some kind of notification frames over the widget. Is there any default way to do that?
When I try to design that on creator, I could not get rid of layout limits and don't allow me to place a widget over openglwidget.

When reading the question first, I was reading this as “How to make a QOpenGLWidget with Head-Up Display with buttons?”.
How to make a QOpenGLWidget with Head-Up Display (painted with QPainter), I once answered in SO: Paint a rect on qglwidget at specifit times. However, there was only painting – no interactive widgets.
Hence, I prepared a new sample testQGLWidgetHUDButtons.cc:
#include <QtWidgets>
class OpenGLWidget: public QOpenGLWidget, public QOpenGLFunctions {
private:
struct ClearColor {
float r, g, b;
ClearColor(): r(0.6f), g(0.8f), b(1.0f) { }
} _clearColor;
public:
OpenGLWidget(QWidget *pQParent = nullptr):
QOpenGLWidget(pQParent),
QOpenGLFunctions()
{ }
virtual ~OpenGLWidget() = default;
OpenGLWidget(const OpenGLWidget&) = delete;
OpenGLWidget& operator=(const OpenGLWidget&) = delete;
void setClearColor(float r, float g, float b);
protected:
virtual void initializeGL() override;
virtual void paintGL() override;
};
void OpenGLWidget::setClearColor(float r, float g, float b)
{
_clearColor.r = r; _clearColor.g = g; _clearColor.b = b;
update(); // force update of widget
}
void OpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
}
void OpenGLWidget::paintGL()
{
glClearColor(
_clearColor.r, _clearColor.g, _clearColor.b, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup GUI
OpenGLWidget qWin;
QGridLayout qGrid;
QPushButton qBtn1(QString::fromUtf8("Black BG"));
qGrid.addWidget(&qBtn1, 1, 1);
QPushButton qBtn2(QString::fromUtf8("White BG"));
qGrid.addWidget(&qBtn2, 1, 2);
QPushButton qBtn3(QString::fromUtf8("Blue BG"));
qGrid.addWidget(&qBtn3, 1, 3);
qGrid.setRowStretch(0, 1);
qGrid.setColumnStretch(0, 1);
qWin.setLayout(&qGrid);
qWin.show();
// install signal handlers
QObject::connect(&qBtn1, &QPushButton::clicked,
[&qWin](bool) { qWin.setClearColor(0.0f, 0.0f, 0.0f); });
QObject::connect(&qBtn2, &QPushButton::clicked,
[&qWin](bool) { qWin.setClearColor(1.0f, 1.0f, 1.0f); });
QObject::connect(&qBtn3, &QPushButton::clicked,
[&qWin](bool) { qWin.setClearColor(0.6f, 0.8f, 1.0f); });
// runtime loop
return app.exec();
}
and the project file testQGLWidgetHUDButtons.pro:
SOURCES = testQGLWidgetHUDButtons.cc
QT += widgets opengl
Compiled and tested in VS2013 on Windows 10:
It's actually quite easy – QOpenGLWidget is derived from QWidget. QWidget provides the method QWidget::setLayout(). To add child widgets, a QLayout should be used which manages layout of children in parent widget (and takes part in size negotiation when parent widget is layouted).
To keep it simple, I used a QGridLayout where buttons are placed in cells (1, 1), (1, 2), and (1, 3). Row 0 and column 0 are left empty but enabled for stetching. This results effectively in attaching the buttons in lower right corner.
When I was about to publish this answer, I took a second look onto the question and realized the Qt Button Group in title. I would've ignore it (multiple buttons are a button group?) but there is also the qbuttongroup which made me stumbling.
A QButtonGroup:
The QButtonGroup class provides a container to organize groups of button widgets.
QButtonGroup provides an abstract container into which button widgets can be placed. It does not provide a visual representation of this container (see QGroupBox for a container widget), but instead manages the states of each of the buttons in the group.
(Emphasize mine.)
So, a button group can be used e.g. to keep radio buttons in sync. It is derived from QObject and hence, neither a QWidget nor a QLayout. Thus, it's not suitable to add visible child widgets to the QOpenGLWidget.
Though I've no experiences with QCreator, I assume it should work there as well as it does in my sample:
Assign a layout to the QOpenGLWidget (e.g. QGridLayout as I did in my sample).
Add QPushButtons to this layout.
Btw. IMHO this is no specific problem about the QOpenGLWidget – it should've happend with any other QWidget as well.

Related

QOpenGLWidget and transparency

I have small problem with QOpenGLWidget and its background color.
When I want to create semi-transparent rect on my custom QOpenGLWidget using QPainter there are 2 different results:
If MyCustomWidget have parent - on every update rect's color multiplies (and after few repaints it is opaque, like previous painting result not cleaned)
If MyCustomWidget doesn't have parent - color doesn't repaints each time
Here is code example for QPainter:
class Widget : public QOpenGLWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0)
: QOpenGLWidget(parent)
{
resize(800, 600);
Test *test = new Test(this);
}
~Widget(){}
protected:
void paintEvent(QPaintEvent *) {}
protected:
void initializeGL() {
if(paintEngine()->type() != QPaintEngine::OpenGL &&
paintEngine()->type() != QPaintEngine::OpenGL2)
qDebug() << "ERROR. Type is: " << paintEngine()->type();
}
void resizeGL(int, int) {}
void paintGL() {
QPainter p;
p.begin(this);
{
p.fillRect(rect(), Qt::white);
}
p.end();
}
private:
class Test : public QOpenGLWidget
{
public:
Test(QWidget *parent = 0) : QOpenGLWidget(parent) {
resize(100, 100);
}
protected:
void paintEvent(QPaintEvent *) {
QPainter p(this);
p.fillRect(rect(), QColor(125, 125, 125, 255/10));
}
};
};
Also by default it has black background (I don't know how to fix it. setAttribute(Qt::WA_TranslucentBackground) doesn't helps).
Also, when I'm trying to clear color using glClear it ignores alpha (both on QOpenGLWidget with parent and not). Here is Test class from previous code, but now it is using opengl to clear color:
class Test : public QOpenGLWidget
{
public:
Test(QWidget *parent = 0) : QOpenGLWidget(parent) {
resize(100, 100);
}
void initializeGL() {
QOpenGLFunctions *f = context()->functions();
f->glClearColor(0.0f, 1.0f, 0.0f, 0.1f);
}
void paintGL() {
QOpenGLFunctions *f = context()->functions();
f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
};
How can I fix this problems?
I'm using Qt 5.5.0, Windows 10, MinGW 4.9.2
Xeed is correct when saying the QOpenGLWidget is painted first.
I'm not an expert but I think I found the solution. You need to set a widget attribute to always make the widget stacked on top (think of the widgets as layers on the window). Here is a link to where I got the following information:
P.S. As mentioned in the QQuickWidget post, there is a limitation regarding semi-transparency when using QQuickWidget or QOpenGLWidget as child widgets. For applications that absolutely need this, Qt 5.4 offers a workaround: the newly introduced Qt::WA_AlwaysStackOnTop widget attribute. This, at the expense of breaking the stacking order for other types of layouts, makes it possible to have a semi-transparent QQuickWidget or QOpenGLWidget with other widgets visible underneath. Of course, if the intention is only to make other applications on the desktop visible underneath, then the Qt::WA_TranslucentBackground attribute is sufficient
Solution in Python:
set attribute of OpenGL widget
setAttribute(Qt.WA_AlwaysStackOnTop)
Now the OpenGL widget is considered 'on top' in the window. Use 'glClearColor' function and specify the alpha channel to be zero (0.0).
glClearColor(0.0, 0.0, 0.0, 0.0)
I'm not sure how to write that in other languages but this worked for me. The OpenGL widget no longer has the default black background. It is transparent! Hope this helps.
As far as I know the QOpenGLWidget is always drawn first. Therefore you cannot show any widgets layered below. I'm currently looking into the same issue. I'll report back, when I find any solution.
I've had similar issue with QOpenGLWidget not repainting correctly in transparent areas and decided to switch to QOpenGLWindow wrapped inside QWidget::createWindowContainer()

Efficient way of displaying a continuous stream of QImages

I am currently using a QLabel to do this, but this seems to be rather slow:
void Widget::sl_updateLiveStreamLabel(spImageHolder_t _imageHolderShPtr) //slot
{
QImage * imgPtr = _imageHolderShPtr->getImagePtr();
m_liveStreamLabel.setPixmap( QPixmap::fromImage(*imgPtr).scaled(this->size(), Qt::KeepAspectRatio, Qt::FastTransformation) );
m_liveStreamLabel.adjustSize();
}
Here I am generating a new QPixmap object for each new image that arrives. Since QPixmap operations are restricted to the GUI Thread, this also makes the GUI feel poorly responsive.
I've seen there are already some discussions on this, most of them advising to use QGraphicsView or QGLWidget, but I have not been able to find a quick example how to properly use those, which would be what I am looking for.
I'd appreciate any help.
QPixmap::fromImage is not the only problem. Using QPixmap::scaled or QImage::scaled also should be avoided. However you can't display QImage directly in QLabel or QGraphicsView. Here is my class that display QImage directly and scales it to the size of the widget:
Header:
class ImageDisplay : public QWidget {
Q_OBJECT
public:
ImageDisplay(QWidget* parent = 0);
void setImage(QImage* image);
private:
QImage* m_image;
protected:
void paintEvent(QPaintEvent* event);
};
Source:
ImageDisplay::ImageDisplay(QWidget *parent) : QWidget(parent) {
m_image = 0;
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
}
void ImageDisplay::setImage(QImage *image) {
m_image = image;
repaint();
}
void ImageDisplay::paintEvent(QPaintEvent*) {
if (!m_image) { return; }
QPainter painter(this);
painter.drawImage(rect(), *m_image, m_image->rect());
}
I tested it on 3000x3000 image scaled down to 600x600 size. It gives 40 FPS, while QLabel and QGraphicsView (even with fast image transformation enabled) gives 15 FPS.
Setting up a QGraphicsView and QGraphicsScene is quite straight-forward: -
int main( int argc, char **argv )
{
QApplication app(argc, argv);
// Create the scene and set its dimensions
QGraphicsScene scene;
scene.setSceneRect( 0.0, 0.0, 400.0, 400.0 );
// create an item that will hold an image
QGraphicsPixmapItem *item = new QGraphicsPixmapItem(0);
// load an image and set it to the pixmapItem
QPixmap pixmap("pathToImage.png") // example filename pathToImage.png
item->setPixmap(pixmap);
// add the item to the scene
scene.addItem(item);
item->setPos(200,200); // set the item's position in the scene
// create a view to look into the scene
QGraphicsView view( &scene );
view.setRenderHints( QPainter::Antialiasing );
view.show();
return app.exec();
}
I recommend not use QLabel but write own class. Every call of setPixmap causes layout system to recalculate sizes of items and this can propagate to topmost parent (QMainWindow) and this is quite big overhead.
Conversion and scaling also is a bit costly.
Finally best approach is to use profiler to detect where is the biggest problem.

How to create a draggable (borderless and titleless) top level window in QT

I'd appreciate help creating a top-level window in Qt with the following characteristics. The window must be:
Borderless, titleless and lie on top of all other windows on the desktop (easy)
Draggable by clicking and dragging anywhere inside it (this what I need help with)
Constrained to the top border of the desktop while dragging (relatively easy)
Basically, I'm trying to collapse our QT application to a top-level icon on the top border of the desktop.
You'll find the answer to the first part in: Making a borderless window with for Qt, and the answer to the second part in Select & moving Qwidget in the screen.
Combining the two, and adding the last part is straightforward.
Here's how you could do it:
#include <QtGui>
class W: public QWidget
{
Q_OBJECT
Set up a borderless widget with a few buttons to lock/unlock and quit:
public:
W(QWidget *parent=0)
: QWidget(parent, Qt::FramelessWindowHint), locked(false)
{
QPushButton *lock = new QPushButton("Lock");
QPushButton *unlock = new QPushButton("Unlock");
QPushButton *quit = new QPushButton("&Quit");
connect(lock, SIGNAL(clicked()), this, SLOT(lock()));
connect(unlock, SIGNAL(clicked()), this, SLOT(unlock()));
connect(quit, SIGNAL(clicked()),
QApplication::instance(), SLOT(quit()));
QHBoxLayout *l = new QHBoxLayout;
l->addWidget(lock);
l->addWidget(unlock);
l->addWidget(quit);
setLayout(l);
}
public slots:
void lock() {
locked = true;
move(x(), 0); // move window to the top of the screen
}
void unlock() { locked = false; }
Do the mouse handling:
protected:
void mousePressEvent(QMouseEvent *evt)
{
oldPos = evt->globalPos();
}
void mouseMoveEvent(QMouseEvent *evt)
{
const QPoint delta = evt->globalPos() - oldPos;
if (locked)
// if locked, ignore delta on y axis, stay at the top
move(x()+delta.x(), y());
else
move(x()+delta.x(), y()+delta.y());
oldPos = evt->globalPos();
}
private:
bool locked;
QPoint oldPos;
};

Sliding OSX Dock type "sidebar" rather embedded QDockWidget type?

I want to make a sliding "sidebar" similar to the functionality of the OSX "Dock" (e.g. mouse passes edge of screen and Dock slides out). I've been playing around with QDockWidget but since that is embedded in the window layout, it causes everything to shift when it becomes visible.
Can someone suggest a way to implement this?
Doesn't need to float (as a separate window/tool bar)
Should scale to window height (e.g. window can be fullscreen or default size)
Doesn't need to slide (animate) if that is complicated.
I'm new to Qt and so don't want to over-think this. Is this just a matter of a custom widget or should I be looking at a borderless window? The custom widget approach seems right but I don't know how to specify that it overlay other window content and also scale if the window scales.
QDockWidget has nothing to do with what you want - behaviorally. Just because it's called a Dock widget doesn't mean it's the same "Dock" concept as in OS X. It merely means that it docks somewhere. QDockWidget's documentation quite explicitly explains what is meant by the docking behavior.
The code below implements the behavior you seem to want. Whether it's good design or not is arguable. The reason the code is "convoluted" seems to hint that nobody is expected to come up with such a UI design. What's wrong with actually clicking a button somewhere to display the slider window?
The code works under both Qt 4.8 and 5.1.
Note: This begs to be implemented in Qt Quick 2. That's what it was designed for :) Of course Qt 4.6+ improved the behavior of the QWidget-moving animations, and Qt 5 does further tweaks, but really this code smells bad and there's a good reason it does: QWidget API, while powerful, ultimately encapsulates a set of APIs that date to 1984 when the original Macintosh was released. There's only so much you can do when you have to composite results from a bunch of stacked painters. In Qt Quick, the rendering is done by the GPU. The animation amounts to passing a couple of new floats to the GPU to update a single transformation matrix. That's it.
#include <QApplication>
#include <QWidget>
#include <QGridLayout>
#include <QLabel>
#include <QPainter>
#include <QGradient>
#include <QMouseEvent>
#include <QPropertyAnimation>
class Slider : public QWidget {
void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE {
QPainter p(this);
QLinearGradient g(QPointF(0,0), QPointF(rect().bottomRight()));
g.setColorAt(0, Qt::blue);
g.setColorAt(1, Qt::gray);
p.setBackground(g);
p.eraseRect(rect());
p.setPen(Qt::yellow);
p.setFont(QFont("Helvetica", 48));
p.drawText(rect(), "Click Me To Hide");
}
void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE {
hide();
}
public:
explicit Slider(QWidget *parent = 0) : QWidget(parent) {
setAttribute(Qt::WA_OpaquePaintEvent);
}
};
class Window : public QWidget {
QGridLayout m_layout;
Slider m_slider;
QLabel m_label;
QPropertyAnimation m_animation;
public:
explicit Window(QWidget *parent = 0, Qt::WindowFlags f = 0) :
QWidget(parent, f),
m_layout(this),
m_slider(this),
m_animation(&m_slider, "pos")
{
setMouseTracking(true);
m_layout.addWidget(&m_label);
m_slider.hide();
m_slider.setMouseTracking(false);
m_animation.setStartValue(QPoint(-width(), 0));
m_animation.setEndValue(QPoint(0, 0));
m_animation.setDuration(500);
m_animation.setEasingCurve(QEasingCurve::InCubic);
}
void leaveEvent(QEvent *) {
if (window() && QCursor::pos().x() <= window()->geometry().topLeft().x()) {
showSlider();
}
}
void childEvent(QChildEvent * ev) {
if (ev->added() && ev->child()->isWidgetType()) {
ev->child()->installEventFilter(this);
static_cast<QWidget*>(ev->child())->setMouseTracking(true);
}
}
bool event(QEvent * ev) {
eventFilter(this, ev);
return QWidget::event(ev);
}
bool eventFilter(QObject *, QEvent * ev) {
if (ev->type() == QEvent::MouseMove) {
auto pos = QCursor::pos();
if (window() && window()->isFullScreen()) {
if (pos.x() <= window()->geometry().topLeft().x()) {
showSlider();
}
}
m_label.setText(QString("%1, %2").arg(pos.x()).arg(pos.y()));
}
return false;
}
void resizeEvent(QResizeEvent *) {
m_slider.resize(size());
m_animation.setStartValue(QPoint(-width(), 0));
}
Q_SLOT void showSlider() {
if (m_slider.isVisible() || (window() && qApp->activeWindow() != window())) return;
m_slider.raise();
m_slider.show();
m_animation.start();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}

Adding a QSizeGrip to the corner of a QLabel

I'm attempting to produce a widget that consists of a text display that can be resized by the user grabbing the lower right corner. So far I've been able to generate this:
I've applied a red background to the layout to make it more obvious what's going on. I've used the following code to generate this:
m_sizeGrip = new QSizeGrip( this );
m_layout = new QHBoxLayout( this );
m_label = new QLabel( this );
m_layout->setContentsMargins( QMargins() );
m_layout->setSpacing( 0 );
m_layout->addWidget( m_label );
m_layout->addWidget( m_sizeGrip, 0, Qt::AlignBottom | Qt::AlignRight );
setWindowFlags( Qt::SubWindow );
Basically, it's a horizontal layout with the label and grip added to it, which is then installed on a QWidget. My problem is that I'd like the grip to be on the lower right corner of the label, rather than the parent widget. I'd also like to make it invisible while keeping it enabled.
Or perhaps I'm going about this the wrong way. My ultimate goal is to have a textual display widget that can be resized by the user either horizontally or vertically, but doesn't have a visible grip that would obscure the text. Am I already on the right track with the code above, or is there a better way to achieve this?
You could create a custom QLabel for that. The idea would be to track mouse move events (which by default only fire when a mouse button is pressed), and resize based on how much the mouse traveled since the last event.
This allows you to control exactly how to display the "gripper" (if at all) and what shape it should have. You can constrain the resizing to vertical or horizontal (or not).
Here's a demo of how you could do that (resizes both ways). Warning: this might wreak havoc in your layout.
#include <QtGui>
class GripLabel: public QLabel
{
Q_OBJECT
public:
GripLabel(QString const& title, QWidget* parent = 0)
: QLabel(title, parent),
resizing(false),
gripSize(10, 10)
{
// Prevent the widget from disappearing altogether
// Bare minimum would be gripSize
setMinimumSize(100, 30);
}
QSize sizeHint() const
{
return minimumSize();
}
protected:
bool mouseInGrip(QPoint mousePos)
{
// "handle" is in the lower right hand corner
return ((mousePos.x() > (width() - gripSize.width()))
&& (mousePos.y() > (height() - gripSize.height())));
}
void mousePressEvent(QMouseEvent *e)
{
// Check if we hit the grip handle
if (mouseInGrip(e->pos())) {
oldPos = e->pos();
resizing = true;
} else {
resizing = false;
}
}
void mouseMoveEvent(QMouseEvent *e)
{
if (resizing) {
// adapt the widget size based on mouse movement
QPoint delta = e->pos() - oldPos;
oldPos = e->pos();
setMinimumSize(width()+delta.x(), height()+delta.y());
updateGeometry();
}
}
void paintEvent(QPaintEvent *e)
{
QLabel::paintEvent(e);
QPainter p(this);
p.setPen(Qt::red);
p.drawRect(width()-gripSize.width(), height()-gripSize.height(),
gripSize.width(), gripSize.height());
}
private:
bool resizing;
QSize gripSize;
QPoint oldPos;
};
Sample main:
#include "griplabel.h"
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QWidget *w = new QWidget;
QPushButton *b = new QPushButton("button");
GripLabel *l = new GripLabel("Hello");
QHBoxLayout *y = new QHBoxLayout;
y->addWidget(b);
y->addWidget(l);
y->setSizeConstraint(QLayout::SetFixedSize);
w->setLayout(y);
w->show();
return app.exec();
}

Resources