I have a parent container (MyCartParentWidget) with translucent background, inside which I have to draw a child widget (MyCart) with an image background (this image is in portrait, this image is in landscape), also drawn with translucent background, and both being QLabels. There is a button clicking on which, the child widget toggles its dimensions (resetCartStyle), i.e it goes from portrait to landscape mode and vice versa. Problem is, when it toggles, the original imprint stays back, i.e, this is the original pic where it is in 'portrait' mode:
Then when I switch to 'landscape' mode, it does shift, but the original 'portrait' mode stays back:
This is my code:
main.cpp:
#include <QApplication>
#include "MyCartParentWidget.hpp"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyCartParentWidget p;
p.move(370,10);
p.show();
return a.exec();
}
MyCart.cpp:
#include "MyCart.hpp"
#include <QPainter>
MyCart::MyCart(QWidget *parent): QLabel(parent)
{
setAttribute(Qt::WA_TranslucentBackground);
fPixMap.load("/Users/attitude/Desktop/RnSghvV.png");
setStyleSheet("background-color: rgba(0,0,0,255);");
setFixedSize(325,400);
}
void MyCart::paintEvent(QPaintEvent *)
{
QPainter p(this);
p.setRenderHint(QPainter::SmoothPixmapTransform);
p.drawPixmap(0,0,width(),height(),fPixMap);
}
void MyCart::resetCartStyle(QString url, int w, int h)
{
setFixedSize(w,h);
fPixMap.load(url);
this->update();
}
MyCart.hpp:
#pragma once
#include <QLabel>
#include <QPaintEvent>
#include <QPixmap>
class MyCart: public QLabel
{
public:
MyCart(QWidget*);
virtual void paintEvent(QPaintEvent *);
QPixmap fPixMap;
void resetCartStyle(QString, int w, int h);
};
MyCartParentWidget.cpp:
#include "MyCartParentWidget.hpp"
#include <QPushButton>
MyCartParentWidget::MyCartParentWidget()
{
setFixedSize(800,700);
setWindowFlags(Qt::FramelessWindowHint);
setAttribute(Qt::WA_TranslucentBackground);
setStyleSheet("background-color: none;");
fLayout = new QHBoxLayout();
setLayout(fLayout);
fLayout->setContentsMargins(0,0,0,0);
fLayout->setSpacing(0);
fLayout->setMargin(0);
fLayout->setAlignment(Qt::AlignLeft | Qt:: AlignTop);
i = 0;
fCart = new MyCart(this);
fLayout->addWidget(fCart);
QPushButton* p = new QPushButton(this);
p->setText("Toggle");
p->move(0,650);
connect(p,SIGNAL(clicked(bool)),this,SLOT(clickedSlot()));
}
void MyCartParentWidget::clickedSlot()
{
if (i == 0)
{
//enter landscape mode
i = 1;
fCart->resetCartStyle("/Users/attitude/Desktop/foo.png",400,325);
}
else
{
//enter portrait mode
i = 0;
fCart->resetCartStyle("/Users/attitude/Desktop/RnSghvV.png",325,400);
}
}
MyCartParentWidget.hpp:
#pragma once
#include <QLabel>
#include <QHBoxLayout>
#include "MyCart.hpp"
class MyCartParentWidget: public QLabel
{
Q_OBJECT
public:
MyCartParentWidget();
QHBoxLayout* fLayout;
MyCart *fCart;
int i;
private slots:
void clickedSlot();
};
This problem does not happen when I set the background of the parent widget to something like green and comment out the setAttribute(Qt::WA_TranslucentBackground); part, this happens only with setAttribute(Qt::WA_TranslucentBackground); part.
How do I fix this?
Platform - OS X Yosemite, Qt 5.3.1, 32 bit.
Ilya's solution below works fine on Windows, but the problem persists on Mac.
Instead of painting/updating manually, just call the setPixmap method, the QLabel should manage itself. The code is working fine, except on Mac OS X:
MyCart.cpp:
#include "MyCart.hpp"
MyCart::MyCart(QWidget *parent): QLabel(parent)
{
resetCartStyle("C:/dev/cart/portrait.png");
}
void MyCart::resetCartStyle(QString url)
{
fPixMap.load(url);
setPixmap(fPixMap);
}
MyCart.hpp:
#pragma once
#include <QLabel>
#include <QPaintEvent>
#include <QPixmap>
class MyCart: public QLabel
{
public:
MyCart(QWidget*);
QPixmap fPixMap;
void resetCartStyle(QString);
};
MyCartParentWidget.cpp:
#include "MyCartParentWidget.hpp"
#include <QPushButton>
MyCartParentWidget::MyCartParentWidget()
{
setFixedSize(800,700);
setWindowFlags(Qt::FramelessWindowHint);
setAttribute(Qt::WA_TranslucentBackground);
fLayout = new QHBoxLayout();
setLayout(fLayout);
fLayout->setContentsMargins(0,0,0,0);
fLayout->setSpacing(0);
fLayout->setMargin(0);
fLayout->setAlignment(Qt::AlignLeft | Qt:: AlignTop);
i = 0;
fCart = new MyCart(this);
fLayout->addWidget(fCart);
QPushButton* p = new QPushButton(this);
p->setText("Toggle");
p->move(0,650);
connect(p,SIGNAL(clicked(bool)),this,SLOT(clickedSlot()));
}
void MyCartParentWidget::clickedSlot()
{
if (i == 0)
{
//enter landscape mode
i = 1;
fCart->resetCartStyle("C:/dev/cart/landscape.png");
}
else
{
//enter portrait mode
i = 0;
fCart->resetCartStyle("C:/dev/cart/portrait.png");
}
}
So what about Mac OS ? The result is a bit better with Qt 5.5.1 than with 5.3.1, here's a screenshot (Mac OS 10.11):
So, there's a ghost of the image remaining. To get to a fully correct display,
the simplest and most effective trick is to hide/show the parent widget before/after toggling:
void MyCartParentWidget::clickedSlot()
{
hide();
if (i == 0)
{
//enter landscape mode
i = 1;
fCart->resetCartStyle("C:/dev/cart/landscape.png");
}
else
{
//enter portrait mode
i = 0;
fCart->resetCartStyle("C:/dev/cart/portrait.png");
}
show();
}
For completeness, below are two other tricks found while searching for a fix, that have fixed the code of the MCV exemple but not the production application.
First trick:
MyCart.cpp
MyCart::MyCart(QWidget *parent): QLabel(parent)
{
// do nothing in the constructor
}
MyCartParentWidget.cpp
MyCartParentWidget::MyCartParentWidget()
{
...previous code
// add this line...
QTimer::singleShot( 0, this, SLOT(onclicked() ); // ...to show the widget
}
This code still doesn't work with Qt 5.3.1, the OP's version.
For this Qt version, another fix is necessary (lifted from this bug report). NB that fix doesn't suppress the ghost image for Qt 5.5.1.
void MyCartParentWidget::paintEvent(QPaintEvent *)
{
QPainter p( this );
p.setCompositionMode( QPainter::CompositionMode_Clear );
p.fillRect( this->rect(), Qt::transparent );
}
So for a working code (for the exemple) on Mac OS with both Qt versions (5.3.1 and 5.5.1), you have to use both tricks.
Related
I am having issues moving a QGraphicItem in a custom QGraphicView class. What I would like to be able to to do is select the item by a left mouse click and then move it to where I've done a right mouse click.
I stongly suspect that my problem is that QGraphicsItem::setPos() requires the coordinates to be in parent coordinates, and I'm unsure which for of QMouseEvent::*Pos() to use, and how to convert it to parent coordinates.
Screens shots of what is happening, versus what I what follow the code.
main.cpp: (simple main here, standard test harness)
#include "QtTest.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QtTest w;
w.show();
return a.exec();
}
QtTest.h: (This defines the main application window)
#pragma once
#include <QtWidgets/QMainWindow>
class QGraphicsView;
class QGraphicsScene;
class QGraphicsItem;
class QMouseEvent;
class QtTest : public QMainWindow
{
Q_OBJECT
public:
QtTest(QWidget *parent = Q_NULLPTR);
private:
QGraphicsView* m_gv;
QGraphicsScene* m_pScene;
void setupUI();
};
QtTest.cpp: (implementation of the main application window)
#include "QtTest.h"
#include "testGV.h"
#include <QVariant>
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsItem>
#include <QMainWindow>
#include <QMouseEvent>
#include <QWidget>
QtTest::QtTest(QWidget *parent): QMainWindow(parent)
{
setupUI();
}
void QtTest::setupUI()
{
QWidget *centralWidget;
if (objectName().isEmpty())
setObjectName("QtTestClass");
resize(600, 400);
centralWidget = new QWidget(this);
centralWidget->setObjectName("centralWidget");
m_gv = new testGV(centralWidget);
m_gv->setObjectName("graphicsView");
m_gv->setGeometry(QRect(100, 10, 441, 331));
setCentralWidget(centralWidget);
}
testGV.h: (definition of custom widget)
#pragma once
#include <QGraphicsView>
#include <QGraphicsItem>
class testGV : public QGraphicsView
{
Q_OBJECT
public:
testGV(QWidget* parent);
protected:
void mousePressEvent(QMouseEvent*);
private:
QGraphicsScene* m_pScene;
QGraphicsItem* m_pItem;
void createScene();
};
testGV.cpp: (implementation of custom widget)
#include "testGV.h"
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsItem>
#include <QMouseEvent>
testGV::testGV(QWidget* parent) : QGraphicsView(parent)
{
createScene();
}
void testGV::createScene()
{
m_pScene = new QGraphicsScene();
m_pScene->addRect(QRect(30, 30, 150, 150), QPen(Qt::black), QBrush(Qt::black, Qt::NoBrush));
QGraphicsEllipseItem* pTemp = m_pScene->addEllipse(QRect(0, 0, 15, 15), QPen(Qt::black), QBrush(Qt::red, Qt::SolidPattern));
pTemp->setFlag(QGraphicsItem::ItemIsMovable);
pTemp->setFlag(QGraphicsItem::ItemSendsGeometryChanges);
setScene(m_pScene);
}
void testGV::mousePressEvent(QMouseEvent* pEvent)
{
if (pEvent->button() == 1) // left button click
{
m_pItem = itemAt(pEvent->pos());
}
else if (pEvent->button() == 2) // right button click
{
m_pItem->setPos(pEvent->pos());
m_pScene->update();
}
}
The image on the left is the initial display, when I right click on the red dot and then click in the square at about where the black dot is I get the image on the right. What I'm after is the red dot moving to where I clicked.
The cause of the problem is that the QMouseEvent has the position information in the coordinates of the view but the item uses the coordinates of the scene. The solution in that case is to map the coordinates of the view to the coordinates of the scene:
void testGV::mousePressEvent(QMouseEvent* pEvent)
{
if (pEvent->button() == Qt::LeftButton)
m_pItem = itemAt(pEvent->pos());
else if (pEvent->button() == Qt::RightButton)
if(m_pItem)
m_pItem->setPos(mapToScene(pEvent->pos()));
}
But even so there is a problem, the position for your items is with respect to the topLeft so the displacement will have an error, if you want to avoid that deviation then you must consider the position of the left click:
void testGV::mousePressEvent(QMouseEvent* pEvent)
{
if (pEvent->button() == Qt::LeftButton){
m_pItem = itemAt(pEvent->pos());
m_pItem->setData(0, mapToScene(pEvent->pos()));
}
else if (pEvent->button() == Qt::RightButton)
if(m_pItem){
QPointF p = m_pItem->data(0).toPointF();
QPointF sp = mapToScene(pEvent->pos());
m_pItem->setPos(m_pItem->pos() + sp - p);
m_pItem->setData(0, sp);
}
}
Note: When a pointer is created it points to any memory location so that can cause problems so I recommend initializing it to nullptr and also checking if the pointer is valid:
testGV::testGV(QWidget* parent) : QGraphicsView(parent), m_pItem(nullptr)
{
createScene();
}
I have derived the class of QTabBar to implement "+" (new tab button) button using QToolButton (similar to google chrome). However, it is working in my Linux machine but doesn't work in my windows machine. By not working I mean QToolButton is not visible in my windows machine but it is visible in my Linux machine (Ubuntu). I am not able to debug it further as I have tried few experiments to understand the reason but it didn't work.
My Source file:
#include "tabbar.h"
TabBar::TabBar(QWidget *parent) : QTabBar(parent)
{
new_button_ = new QToolButton(this);
new_button_->setObjectName(QStringLiteral("AddButton"));
new_button_->setText("+");
new_button_->setFixedSize(QSize(20, 20));
connect(new_button_, SIGNAL(released()), this, SLOT(emit_new()));
movePlusButton();
}
QSize TabBar::sizeHint(void) const
{
QSize old = QTabBar::sizeHint();
return QSize(old.width() + 45, old.height());
}
void TabBar::emit_new(void)
{
emit newClicked();
}
void TabBar::movePlusButton(void)
{
quint64 totalWidth = 0;
for (long i=0; i < count(); i++)
totalWidth += tabRect(i).width();
quint64 h = geometry().top();
quint64 tab_height = height();
quint64 w = width();
if (totalWidth > w)
new_button_->move(w-40, tab_height - 30);
else
new_button_->move(totalWidth + 5, tab_height - 30);
}
void TabBar::resizeEvent(QResizeEvent *p_evt)
{
QTabBar::resizeEvent(p_evt);
movePlusButton();
}
void TabBar::tabLayoutChange(void)
{
QTabBar::tabLayoutChange();
movePlusButton();
}
My Header File:
#ifndef TABBAR_H
#define TABBAR_H
#include <QObject>
#include <QToolButton>
#include <QTabBar>
#include <QResizeEvent>
#include <QLabel>
class TabBar : public QTabBar {
Q_OBJECT
public:
TabBar(QWidget *parent=nullptr);
~TabBar() { }
void movePlusButton(void);
void resizeEvent(QResizeEvent *p_evt) override;
void tabLayoutChange(void) override;
QSize sizeHint(void) const override;
private slots:
void emit_new(void);
signals:
void newClicked(void);
private:
QToolButton *new_button_;
};
#endif // TABBAR_H
EDIT:
I have tried few more experiments and got to know QToolButton is hiding behind region next to Tab bars. Please refer the screenshot.
Apparently, your application uses a stylesheet or something to customize the display and this is incompatible with your TabBar class.
Downloaded your code, wrote a simple main:
#include <QApplication>
#include <QMainWindow>
#include "tabbar.h"
int main( int argc, char* argv[] )
{
QApplication app(argc, argv);
QMainWindow wnd;
TabBar* tabBar = new TabBar(&wnd);
wnd.setCentralWidget( tabBar );
tabBar->addTab( "Foo" );
wnd.show();
return app.exec();
}
compiled and executed on Windows and got that (tested classic and aero style):
So apparently your code is fine. However, if you customized the QTabBar rendering through a stylesheet (what I suspect when I see how it looks in your GUI), you may need to adapt yourcode (probably movePlusButton as it has some values hardcoded that may be incompatible with the current display style):
if (totalWidth > w)
new_button_->move(w-40, tab_height - 30);
else
new_button_->move(totalWidth + 5, tab_height - 30);
I have a Windows & Mac program that switches into full-screen mode on multiple monitors. In Qt 4, it seems (I can't find explicit documentation on how to do this) like the 'correct' way to go about this is by creating N QMainWindow's for the N monitors on the machine, calling QWidget::move() to the N monitor's top-left x,y coordinates, and then calling QWidget::setWindowState(Qt::WindowFullScreen). I don't know whether this is The Right Thing To Do - again, I can't find any documentation or examples anywhere that do this in Qt.
This seems to be 'broken' (if it was ever the Right Thing To Do in the first place) in Qt 5.4.1, especially on Windows 7. I'm still trying to isolate the problem, but it seems like the QMainWindows are dropping out of full-screen mode.
Just so I'm clear about this, what is the right way to do this? I found this forum post which seems to suggest that I should be setting the QScreen on the underlying QWindow objects that are held by the QMainWindows, but this doesn't seem to work in my tests. Here's an example program that I wrote:
app.h:
#include <vector>
#include <QObject>
class QMainWindow;
class app : public QObject
{
Q_OBJECT
public:
int run(int argc, char** argv);
public slots:
void add_window();
void remove_window();
void windows_go_to_screens();
void windows_go_to_screens_old();
void windows_go_to_primary_screen();
void windows_fullscreen();
void windows_nonfullscreen();
private:
QMainWindow * create_window(const char * id);
void init_menus( QMainWindow * w );
std::vector<QMainWindow *> m_windows;
};
app.cpp:
#include <assert.h>
#include <algorithm>
#include <iostream>
#include <vector>
#include <QObject>
#include <QMainWindow>
#include <QApplication>
#include <QMenubar>
#include <QAction>
#include <QScreen>
#include <QWindow>
#include <QLayout>
#include <QLabel>
#include <QStyle>
#include "app.h"
using namespace std;
int app::run(int argc, char** argv)
{
QApplication a(argc, argv);
QMainWindow * w = create_window("0");
m_windows.push_back(w);
w->show();
return a.exec();
}
void app::add_window()
{
static const char * nums[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};
m_windows.push_back(create_window(nums[m_windows.size()]));
m_windows.back()->show();
}
void app::remove_window()
{
if (m_windows.size() > 1)
{
QMainWindow * w = m_windows.back();
m_windows.pop_back();
w->close();
w->deleteLater();
}
}
void app::windows_go_to_screens()
{
QList<QScreen*> screens = qApp->screens();
for (unsigned i = 0; i < std::min((unsigned)m_windows.size(), (unsigned)screens.size()); ++i)
{
QMainWindow * mw = m_windows[i];
QScreen * screen = screens[i];
QWindow * wh = mw->windowHandle();
wh->setScreen(screen);
}
}
void app::windows_go_to_screens_old()
{
QList<QScreen*> screens = qApp->screens();
for (unsigned i = 0; i < std::min((unsigned)m_windows.size(), (unsigned)screens.size()); ++i)
{
QMainWindow * mw = m_windows[i];
QScreen * screen = screens[i];
mw->move(screen->geometry().left(), screen->geometry().top());
}
}
void app::windows_go_to_primary_screen()
{
QList<QScreen*> screens = qApp->screens();
for (unsigned i = 0; i < std::min((unsigned)m_windows.size(), (unsigned)screens.size()); ++i)
{
QMainWindow * mw = m_windows[i];
QScreen * screen = screens[0];
QWindow * wh = mw->windowHandle();
wh->setScreen(screen);
}
}
void app::windows_fullscreen()
{
for (unsigned i = 0; i < m_windows.size(); ++i)
{
QMainWindow * mw = m_windows[i];
mw->showFullScreen();
}
}
void app::windows_nonfullscreen()
{
for (unsigned i = 0; i < m_windows.size(); ++i)
{
QMainWindow * mw = m_windows[i];
mw->showNormal();
}
}
QMainWindow * app::create_window(const char * id)
{
QMainWindow * w = new QMainWindow(NULL);
init_menus(w);
QWidget * cw = new QWidget(w);
w->setCentralWidget(cw);
QHBoxLayout * l = new QHBoxLayout(cw);
cw->setLayout(l);
QLabel * lab = new QLabel(id, cw);
QPalette pal(lab->palette());
pal.setColor(QPalette::Background, Qt::red);
lab->setAutoFillBackground(true);
lab->setPalette(pal);
lab->setScaledContents(true);
lab->setAlignment(Qt::AlignCenter);
l->addWidget( lab );
return w;
}
void app::init_menus( QMainWindow * w )
{
QMenuBar * menubar = w->menuBar();
QMenu * view_menu = new QMenu(tr("View"), w);
view_menu->addAction("Add Window", this, SLOT(add_window()));
view_menu->addAction("Remove Window", this, SLOT(remove_window()));
view_menu->addAction("Windows Go To Screens", this, SLOT(windows_go_to_screens()));
view_menu->addAction("Windows Go To Screens (old method)", this, SLOT(windows_go_to_screens_old()));
view_menu->addAction("Windows Go To Primary Screen", this, SLOT(windows_go_to_primary_screen()));
view_menu->addAction("Windows Fullscreen", this, SLOT(windows_fullscreen()));
view_menu->addAction("Windows Non-Fullscreen", this, SLOT(windows_nonfullscreen()));
menubar->addMenu(view_menu);
}
main.cpp:
#include "app.h"
int main(int argc, char** argv)
{
app a;
return a.run(argc, argv);
}
When I run this program on OS X, the "Windows Go To Screens" function does nothing - none of the windows move. Neither does the "Windows Go To Primary Screen" (poorly named - should be 0 screen?). Creating more than N windows on an N window Mac is interesting - in that case calling "Windows Fullscreen" several times will actually switch the QMainWindows into fullscreen mode one at a time?!
Even more interesting is what happens on a multi-monitor OS X machine when you do this: "Add Window" until you have as many windows as displays. "Windows Go To Screens (old method)" will send each window to the top-left of each monitor. "Windows Fullscreen" will make all windows go full-screen on all monitors. "Remove Window" until you have only 1 window left. Then "Windows Non-FullScreen", and you'll get an interesting surprise. Go into Mission Control to see what's going on.
Can anyone tell me what the RIGHT way of doing this is? I've looked through the Qt5 examples - there's the player application that seems to be thoroughly broken (it can play a video in full-screen mode once, and then subsequent plays are in a separate desktop window), the sub game only maximizes to a single display, and none of the other examples seem to utilize full-screen mode, and certainly not on multiple monitors.
One way of doing it in Qt5 is to use QWindow::setScreen to set the screen on which the window should be shown. QWidget has a windowHandle() that returns the pointer to the QWindow. So you can get that pointer for each window and set a different screen.
Here is how to show your widget in the last screen in full-screen mode :
QWidget * widget = new QWidget();
widget->show();
widget->windowHandle()->setScreen(qApp->screens().last());
widget->showFullScreen();
Or in the second screen :
QWidget * widget = new QWidget();
widget->show();
widget->windowHandle()->setScreen(qApp->screens()[1]);
widget->showFullScreen();
I have an application where fixed-size child widgets need to be added programatically to a dock widget at run time based on user input. I want to add these widgets to a dock on the Qt::RightDockArea, from top to bottom until it runs out of space, then create a new column and repeat (essentially just the reverse of the flow layout example here, which I call a fluidGridLayout)
I can get the dock widget to resize itself properly using an event filter, but the resized dock's geometry doesn't change, and some of the widgets are drawn outside of the main window. Interestingly, resizing the main window, or floating and unfloating the dock cause it to 'pop' back into the right place (I haven't been able to find a way to replicate this programatically however)
I can't use any of the built-in QT layouts because with the widgets in my real program, they end up also getting drawn off screen.
Is there some way that I can get the dock to update it's top left coordinate to the proper position once it has been resized?
I think this may be of general interest as getting intuitive layout management behavior for dock widgets in QT is possibly the hardest thing known to man.
VISUAL EXMAPLE:
The code to replicate this is example given below.
Add 4 widgets to the program using the button
Resize the green bottom dock until only two widgets are shown. Notice that the 3 remaining widgets are getting painted outside the main window, however the dock is the right size, as evidenced by the fact that you can't see the close button anymore
Undock the blue dock widget. Notice it snaps to it's proper size.
Re-dock the blue dock to the right dock area. Notice it appears to be behaving properly now.
Now resize the green dock to it's minimum size. Notice the dock is now IN THE MIDDLE OF THE GUI. WTf, how is this possible??
THE CODE
Below I give the code to replicate the GUI from the screenshots.
main.cpp:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "QFluidGridLayout.h"
#include "QDockResizeEventFilter.h"
#include <QDockWidget>
#include <QGroupBox>
#include <QPushButton>
#include <QWidget>
#include <QDial>
class QTestWidget : public QGroupBox
{
public:
QTestWidget() : QGroupBox()
{
setFixedSize(50,50);
setStyleSheet("background-color: red;");
QDial* dial = new QDial;
dial->setFixedSize(40,40);
QLayout* testLayout = new QVBoxLayout;
testLayout->addWidget(dial);
//testLayout->setSizeConstraint(QLayout::SetMaximumSize);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
setLayout(testLayout);
}
QSize sizeHint()
{
return minimumSize();
}
QDial* dial;
};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QDockWidget* rightDock = new QDockWidget();
QDockWidget* bottomDock = new QDockWidget();
QGroupBox* central = new QGroupBox();
QGroupBox* widgetHolder = new QGroupBox();
QGroupBox* placeHolder = new QGroupBox();
placeHolder->setStyleSheet("background-color: green;");
placeHolder->setMinimumHeight(50);
widgetHolder->setStyleSheet("background-color: blue;");
widgetHolder->setMinimumWidth(50);
widgetHolder->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
widgetHolder->setLayout(new QFluidGridLayout);
widgetHolder->layout()->addWidget(new QTestWidget);
QPushButton* addWidgetButton = new QPushButton("Add another widget");
connect(addWidgetButton, &QPushButton::pressed, [=]()
{
widgetHolder->layout()->addWidget(new QTestWidget);
});
central->setLayout(new QVBoxLayout());
central->layout()->addWidget(addWidgetButton);
rightDock->setWidget(widgetHolder);
rightDock->installEventFilter(new QDockResizeEventFilter(widgetHolder,dynamic_cast<QFluidGridLayout*>(widgetHolder->layout())));
bottomDock->setWidget(placeHolder);
this->addDockWidget(Qt::RightDockWidgetArea, rightDock);
this->addDockWidget(Qt::BottomDockWidgetArea, bottomDock);
this->setCentralWidget(central);
central->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
this->setMinimumSize(500,500);
}
};
QFluidGirdLayout.h
#ifndef QFluidGridLayout_h__
#define QFluidGridLayout_h__
#include <QLayout>
#include <QGridLayout>
#include <QRect>
#include <QStyle>
#include <QWidgetItem>
class QFluidGridLayout : public QLayout
{
public:
enum Direction { LeftToRight, TopToBottom};
QFluidGridLayout(QWidget *parent = 0)
: QLayout(parent)
{
setContentsMargins(8,8,8,8);
setSizeConstraint(QLayout::SetMinAndMaxSize);
}
~QFluidGridLayout()
{
QLayoutItem *item;
while ((item = takeAt(0)))
delete item;
}
void addItem(QLayoutItem *item)
{
itemList.append(item);
}
Qt::Orientations expandingDirections() const
{
return 0;
}
bool hasHeightForWidth() const
{
return false;
}
int heightForWidth(int width) const
{
int height = doLayout(QRect(0, 0, width, 0), true, true);
return height;
}
bool hasWidthForHeight() const
{
return true;
}
int widthForHeight(int height) const
{
int width = doLayout(QRect(0, 0, 0, height), true, false);
return width;
}
int count() const
{
return itemList.size();
}
QLayoutItem *itemAt(int index) const
{
return itemList.value(index);
}
QSize minimumSize() const
{
QSize size;
QLayoutItem *item;
foreach (item, itemList)
size = size.expandedTo(item->minimumSize());
size += QSize(2*margin(), 2*margin());
return size;
}
void setGeometry(const QRect &rect)
{
QLayout::setGeometry(rect);
doLayout(rect);
}
QSize sizeHint() const
{
return minimumSize();
}
QLayoutItem *takeAt(int index)
{
if (index >= 0 && index < itemList.size())
return itemList.takeAt(index);
else
return 0;
}
private:
int doLayout(const QRect &rect, bool testOnly = false, bool width = false) const
{
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom);
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
int x = effectiveRect.x();
int y = effectiveRect.y();
int lineHeight = 0;
int lineWidth = 0;
QLayoutItem* item;
foreach(item,itemList)
{
QWidget* widget = item->widget();
if (y + item->sizeHint().height() > effectiveRect.bottom() && lineWidth > 0)
{
y = effectiveRect.y();
x += lineWidth + right;
lineWidth = 0;
}
if (!testOnly)
{
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
}
y += item->sizeHint().height() + top;
lineHeight = qMax(lineHeight, item->sizeHint().height());
lineWidth = qMax(lineWidth, item->sizeHint().width());
}
if (width)
{
return y + lineHeight - rect.y() + bottom;
}
else
{
return x + lineWidth - rect.x() + right;
}
}
QList<QLayoutItem *> itemList;
Direction dir;
};
#endif // QFluidGridLayout_h__
QDockResizeEventFilter.h
#ifndef QDockResizeEventFilter_h__
#define QDockResizeEventFilter_h__
#include <QObject>
#include <QLayout>
#include <QEvent>
#include <QDockWidget>
#include <QResizeEvent>
#include "QFluidGridLayout.h"
class QDockResizeEventFilter : public QObject
{
public:
QDockResizeEventFilter(QWidget* dockChild, QFluidGridLayout* layout, QObject* parent = 0)
: QObject(parent), m_dockChild(dockChild), m_layout(layout)
{
}
protected:
bool eventFilter(QObject *p_obj, QEvent *p_event)
{
if (p_event->type() == QEvent::Resize)
{
QResizeEvent* resizeEvent = static_cast<QResizeEvent*>(p_event);
QMainWindow* mainWindow = static_cast<QMainWindow*>(p_obj->parent());
QDockWidget* dock = static_cast<QDockWidget*>(p_obj);
// determine resize direction
if (resizeEvent->oldSize().height() != resizeEvent->size().height())
{
// vertical expansion
QSize fixedSize(m_layout->widthForHeight(m_dockChild->size().height()), m_dockChild->size().height());
if (dock->size().width() != fixedSize.width())
{
m_dockChild->resize(fixedSize);
m_dockChild->setFixedWidth(fixedSize.width());
dock->setFixedWidth(fixedSize.width());
mainWindow->repaint();
//dock->setGeometry(mainWindow->rect().right()-fixedSize.width(),dock->geometry().y(),fixedSize.width(), fixedSize.height());
}
}
if (resizeEvent->oldSize().width() != resizeEvent->size().width())
{
// horizontal expansion
m_dockChild->resize(m_layout->sizeHint().width(), m_dockChild->height());
}
}
return false;
}
private:
QWidget* m_dockChild;
QFluidGridLayout* m_layout;
};
#endif // QDockResizeEventFilter_h__
The problem is, nothing in the code above actually causes the QMainWindowLayout to recalculate itself. That function is buried within the QMainWindowLayout private class, but can be stimulated by adding and removing a dummy QDockWidget, which causes the layout to invalidate and recalcualte the dock widget positions
QDockWidget* dummy = new QDockWidget;
mainWindow->addDockWidget(Qt::TopDockWidgetArea, dummy);
mainWindow->removeDockWidget(dummy);
The only problem with this is that if you dig into the QT source code, you'll see that adding a dock widget causes the dock separator to be released, which causes unintuitive and choppy behavior as the user tries to resize the dock, and the mouse unexpectedly 'lets go'.
void QMainWindowLayout::addDockWidget(Qt::DockWidgetArea area,
QDockWidget *dockwidget,
Qt::Orientation orientation)
{
addChildWidget(dockwidget);
// If we are currently moving a separator, then we need to abort the move, since each
// time we move the mouse layoutState is replaced by savedState modified by the move.
if (!movingSeparator.isEmpty())
endSeparatorMove(movingSeparatorPos);
layoutState.dockAreaLayout.addDockWidget(toDockPos(area), dockwidget, orientation);
emit dockwidget->dockLocationChanged(area);
invalidate();
}
That can be corrected by moving the cursor back onto the separator and simulating a mouse press, basically undoing the endSeparatorMove callafter the docks have been repositioned. It's important to post the event, rather than send it, so thatit occurs after the resize event. The code for doing so looks like:
QPoint mousePos = mainWindow->mapFromGlobal(QCursor::pos());
mousePos.setY(dock->rect().bottom()+2);
QCursor::setPos(mainWindow->mapToGlobal(mousePos));
QMouseEvent* grabSeparatorEvent =
new QMouseEvent(QMouseEvent::MouseButtonPress,mousePos,Qt::LeftButton,Qt::LeftButton,Qt::NoModifier);
qApp->postEvent(mainWindow, grabSeparatorEvent);
Where 2 is a magic number that accounts for the group box border.
Put that all together, and here is the event filter than gives the desired behavior:
Corrected Event Filter
#ifndef QDockResizeEventFilter_h__
#define QDockResizeEventFilter_h__
#include <QObject>
#include <QLayout>
#include <QEvent>
#include <QDockWidget>
#include <QResizeEvent>
#include <QCoreApplication>
#include <QMouseEvent>
#include "QFluidGridLayout.h"
class QDockResizeEventFilter : public QObject
{
public:
friend QMainWindow;
friend QLayoutPrivate;
QDockResizeEventFilter(QWidget* dockChild, QFluidGridLayout* layout, QObject* parent = 0)
: QObject(parent), m_dockChild(dockChild), m_layout(layout)
{
}
protected:
bool eventFilter(QObject *p_obj, QEvent *p_event)
{
if (p_event->type() == QEvent::Resize)
{
QResizeEvent* resizeEvent = static_cast<QResizeEvent*>(p_event);
QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(p_obj->parent());
QDockWidget* dock = static_cast<QDockWidget*>(p_obj);
// determine resize direction
if (resizeEvent->oldSize().height() != resizeEvent->size().height())
{
// vertical expansion
QSize fixedSize(m_layout->widthForHeight(m_dockChild->size().height()), m_dockChild->size().height());
if (dock->size().width() != fixedSize.width())
{
m_dockChild->setFixedWidth(fixedSize.width());
dock->setFixedWidth(fixedSize.width());
// cause mainWindow dock layout recalculation
QDockWidget* dummy = new QDockWidget;
mainWindow->addDockWidget(Qt::TopDockWidgetArea, dummy);
mainWindow->removeDockWidget(dummy);
// adding dock widgets causes the separator move event to end
// restart it by synthesizing a mouse press event
QPoint mousePos = mainWindow->mapFromGlobal(QCursor::pos());
mousePos.setY(dock->rect().bottom()+2);
QCursor::setPos(mainWindow->mapToGlobal(mousePos));
QMouseEvent* grabSeparatorEvent = new QMouseEvent(QMouseEvent::MouseButtonPress,mousePos,Qt::LeftButton,Qt::LeftButton,Qt::NoModifier);
qApp->postEvent(mainWindow, grabSeparatorEvent);
}
}
if (resizeEvent->oldSize().width() != resizeEvent->size().width())
{
// horizontal expansion
// ...
}
}
return false;
}
private:
QWidget* m_dockChild;
QFluidGridLayout* m_layout;
};
#endif // QDockResizeEventFilter_h__
I am putting a QProgressBar inside a QSplashScreen by subclassing QSplashScreen. It overrides the drawContents() method.
I thought I had set the geometry correctly, but it renders at both the top and bottom of the screen. I don't know why. Perhaps there's another way to align it. The numbers are correct, as the image is 380x284, so a 19 height progress bar should be 265 pixels down.
Sorry for crappy picture, splash screen wasn't showing up with print screen button. It's just a 1 color white splash screen at the moment, but as you can see, progress bar at top and bottom (they're both the same colors, its the lighting from the camera).
http://i.imgur.com/p1LoJ.jpg
Another issue will be the showMessage() method of QSplashScreen. I want the message to appear above the progress bar, right-aligned... if anyone has any ideas how to do that.
splashscreen.cpp
#include "splashscreen.h"
SplashScreen::SplashScreen(QApplication *app, QWidget *parent) :
QSplashScreen(parent)
{
this->app = app;
this->setPixmap(QPixmap(":/images/splashscreen.png"));
this->setCursor(Qt::BusyCursor);
// if I dont make it a child, it *only* renders at the top
progress = new QProgressBar(this);
progress->setGeometry(0, 265, 380, 19); // puts it at bottom
progress->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
progress->setValue(0);
progress->setMaximum(100);
progress->setEnabled(true);
this->showMessage("Hello", Qt::AlignBottom);
connect(progress, SIGNAL(valueChanged(int)), this, SLOT(progressBarUpdated(int)));
}
void SplashScreen::drawContents(QPainter *painter)
{
QSplashScreen::drawContents(painter);
this->progress->render(painter);
}
void SplashScreen::progressBarUpdated(int value)
{
this->repaint();
this->app->processEvents();
}
splashscreen.h
#ifndef SPLASHSCREEN_H
#define SPLASHSCREEN_H
#include <QSplashScreen>
#include <QProgressBar>
#include <QApplication>
class SplashScreen : public QSplashScreen
{
Q_OBJECT
public:
explicit SplashScreen(QApplication *app, QWidget *parent = 0);
QProgressBar *progress;
QWidget *spacer;
QApplication *app;
public slots:
void progressBarUpdated(int value);
protected:
void drawContents(QPainter *painter);
};
#endif // SPLASHSCREEN_H
main.cpp
#include <QtGui/QApplication>
#include <time.h>
#include "splashscreen.h"
#include "mainwindow.h"
int main(int argc, char *argv[])
{
srand(time(0));
QApplication a(argc, argv);
SplashScreen *splash = new SplashScreen(&a);
splash->show();
// snip.. loading a ton of stuff into memory at startup
// if you're testing this you might have to sleep/timer here iono
MainWindow w;
splash->finish(&w);
w.show();
return app.exec();
}
You can paint progress directly, without creating QProgressBar. For example:
sp.h:
#ifndef SPLASHSCREEN_H
#define SPLASHSCREEN_H
#include <QSplashScreen>
#include <QApplication>
class SplashScreen : public QSplashScreen
{
Q_OBJECT
public:
explicit SplashScreen(QApplication *app, QWidget *parent = 0);
int m_progress;
QApplication *app;
public slots:
void setProgress(int value)
{
m_progress = value;
if (m_progress > 100)
m_progress = 100;
if (m_progress < 0)
m_progress = 0;
update();
}
protected:
void drawContents(QPainter *painter);
};
#endif // SPLASHSCREEN_H
sp.cpp
#include "sp.h"
SplashScreen::SplashScreen(QApplication *aApp, QWidget *parent) :
QSplashScreen(parent), app(aApp), m_progress(0)
{
this->setPixmap(QPixmap(":/images/splashscreen.png"));
this->setCursor(Qt::BusyCursor);
this->showMessage("Hello", Qt::AlignBottom);
}
void SplashScreen::drawContents(QPainter *painter)
{
QSplashScreen::drawContents(painter);
// Set style for progressbar...
QStyleOptionProgressBarV2 pbstyle;
pbstyle.initFrom(this);
pbstyle.state = QStyle::State_Enabled;
pbstyle.textVisible = false;
pbstyle.minimum = 0;
pbstyle.maximum = 100;
pbstyle.progress = m_progress;
pbstyle.invertedAppearance = false;
pbstyle.rect = QRect(0, 265, 380, 19); // Where is it.
// Draw it...
style()->drawControl(QStyle::CE_ProgressBar, &pbstyle, painter, this);
}
May be this helps you.