I'am stuck in one case. I have scene (using QGraphicsScene) and I fill that scene with squares (using QGraphicsRectItem). I want make every square color to black as I move mouse over squares with mouse button pressed. Can you please give me any idea how to make that happen ? I was trying to solve that using mousePressEvent, mouseMoveEvent, dragEnterEvent etc. and I think that this is a proper way to do that but I have no idea how to push that through. To put more light on my case I have added sample of my code. Thanks for help.
main.cpp
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include "square.h"
#include "background.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// create a scene
QGraphicsScene * scene = new QGraphicsScene(0,0,200,250);
Background * background = new Background();
background->fillBackgroundWithSquares(scene);
// add a view
QGraphicsView * view = new QGraphicsView(scene);
view->show();
return a.exec();
}
background.h
#ifndef BACKGROUND_H
#define BACKGROUND_H
#include <QGraphicsScene>
#include <square.h>
class Background
{
public:
Background();
void fillBackgroundWithSquares(QGraphicsScene *scene);
};
#endif // BACKGROUND_H
background.cpp
#include "background.h"
Background::Background()
{
}
void Background::fillBackgroundWithSquares(QGraphicsScene *scene)
{
// create an item to put into the scene
Square *squares[20][25];
// add squares to the scene
for (int i = 0; i < 20; i++)
for (int j = 0; j < 25; j++) {
squares[i][j] = new Square(i*10,j*10);
scene->addItem(squares[i][j]);
}
}
square.h (EDIT)
#ifndef SQUARE_H
#define SQUARE_H
#include <QGraphicsRectItem>
#include <QGraphicsView>
class Square : public QGraphicsRectItem
{
public:
Square(int x, int y);
private:
QPen pen;
protected:
void hoverEnterEvent(QGraphicsSceneHoverEvent * event);
};
#endif // SQUARE_H
square.cpp (EDIT)
#include "square.h"
Square::Square(int x, int y)
{
// draw a square
setRect(x,y,10,10);
pen.setBrush(Qt::NoBrush);
setPen(pen);
setBrush(Qt::cyan);
setAcceptHoverEvents(true);
setAcceptedMouseButtons(Qt::LeftButton);
show();
}
void Square::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
if ( brush().color() != Qt::black && QApplication::mouseButtons() == Qt::LeftButton)
{
setBrush( Qt::black );
update();
}
}
Try calling:
square[j][j]->setAcceptedMouseButtons(...)
and
square[i][j]->show()
after creating it.
You can also reimplement hoverEnterEvent() and hoverLeaveEvent() if you want to change the color on the hover event.
If the mouse butten needs to be pressed when you hover: you store button state within mouse down/up event in a variable for ex. bool isMouseButtonDown and check this in your hover event handler.
You can also use: QApplication::mouseButtons() to check the buttons states.
Related
I am using an embedded platform to code using the Qt framework not Qt creator but it causes problems when I try to run my code I just set the size of my screen and it cause my player character to no longer take any inputs anymore I looked up the problem on Qt website and I found what I need to do use the QEvent::requestsoftwareinputpanel but I don't know how to implement it.
I tried to replace the original code that took inputs but it kept breaking when I tried it
here is my code
this is the main file
#include <QApplication>
#include <QGraphicsScene>
#include "Rect.h"
#include <QGraphicsView>
int main(int argc, char *argv[]){
QApplication a(argc,argv);
//how to make a screen
QGraphicsScene * scene1 = new QGraphicsScene();
// an item to put into the scene
Rect * player = new Rect();
player->setRect(0,0,100,100);
// focus on item
player->setFlag(QGraphicsItem::ItemIsFocusable);
player->setFocus();
//add item to the scene
scene1->addItem(player);
//how to actually see the scene
QGraphicsView * view = new QGraphicsView(scene1);
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// setting screensize
scene1->setSceneRect(0,0,800,600);
view->setBaseSize(800,600);
view->show();
//setting player pos
player->setPos(view->width()/2,view->height() - player->rect().height());
return a.exec();
}
the header file for the movment
//
// Created by krris on 2022-12-07.
//
#ifndef GALAGA_RECT_H
#define GALAGA_RECT_H
#include <QGraphicsRectItem>
#include <QEvent>
class Rect: public QGraphicsRectItem{
void keyPressEvent(QKeyEvent * event);
};
#endif //GALAGA_RECT_H
the file with the code for the movment
//
// Created by krris on 2022-12-07.
//
#include "Rect.h"
#include <QKeyEvent>
#include <QGraphicsScene>
#include "Bullet.h"
#include <QEvent>
void Rect::keyPressEvent(QKeyEvent * event){
if (event->key() == Qt::Key_Left){
QEvent::RequestSoftwareInputPanel;
setPos(x()-10,y());
}
else if (event->key() == Qt::Key_Right){
setPos(x()+10,y());
}
else if (event->key() == Qt::Key_Space){
// create a bullet
Bullet * bullet = new Bullet();
bullet->setPos(x(),y());
scene()->addItem(bullet);
}
}
I'm trying to draw a line using the mouseEvent functions from my QGraphicsScene, when i press the mouse button and move over the scene, the line begin from the top left [0,0] when it should start at the point where i pressed the mouse button, but when i release and do that again the line is drawn normally, what is the reason for this behavior and how to resolve it ?
Here is the full code:
Scene.h:
#ifndef SCENE_H
#define SCENE_H
#include <QGraphicsScene>
#include <QGraphicsLineItem>
#include <QGraphicsSceneMouseEvent>
class Scene : public QGraphicsScene
{
public:
Scene();
private:
QGraphicsLineItem* line;
QPointF startPoint;
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
};
#endif // SCENE_H
Scene.cpp:
#include "Scene.h"
Scene::Scene() : startPoint(0,0)
{
line = new QGraphicsLineItem();
this->addItem(line);
}
void Scene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
startPoint = event->scenePos();
}
void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
qreal x = event->scenePos().x();
qreal y = event->scenePos().y();
line->setLine(startPoint.x(),startPoint.y(),x,y);
}
Main:
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include "Scene.h"
int main(int argc,char* argv[])
{
QApplication app(argc,argv);
Scene scene;
QGraphicsView view(&scene);
view.setMinimumSize(800,600);
view.setAlignment(Qt::AlignLeft|Qt::AlignTop);
view.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
view.show();
return app.exec();
}
The problem is caused because the QGraphicsScene object is not initialized with a bounding box. In the constructor use
Scene::Scene() : QGraphicsScene(0, 0, 400, 400), startPoint(0,0)
(for example), and it will work as expected.
If you do not do this, at the time of the first click the scene coordinates will relocate so that the starting coordinates of your line (say, (x0, y0)) will be the scene coordinates of the top-left corner. So if you move a mouse just a bit, e.g. to (x0+1,y0), the actual scene coordinates will become (2*x0+1,2*y0).
Also, it is good practice to call the parent class' event handlers at the end of your overloaded ones.
Finally, this can be helpful:
https://www.walletfox.com/course/qgraphicsitemruntimedrawing.php
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 a QGraphicsView with a scene containing QGraphicsProxyWidget (QTableView) and a QGraphicsRectItem on top of QTableView. Unfortunately, i am not able to upload any images (Imgur error) but here is a post to visualize how it looks.
My QGraphicsRectItem a vertical line, acts like a seek bar which runs horizontally on the QTableView. Basically, it looks like a time line with a seek bar commonly found in Audio and Video editing tools.
Issues:
Moving the horizontal scroll bar makes the seek bar disappear on hitting the boundaries of QTableView and it doesn't reappear anymore.
While moving the scrollbar if the seekbar hits the boundaries, scrollbar cannot be moved further.
What did i do:
I have managed to create a MVCE (Not really Minimal)
//SeekBar.h
#include <QGraphicsRectItem>
#include <QAbstractItemView>
#include <QObject>
#include <QScrollBar>
#include <QGraphicsProxyWidget>
#include "tableview.h"
class SeekBar : public QObject, public QGraphicsRectItem
{
Q_OBJECT
public:
SeekBar(int width, QTableView* view, QGraphicsScene* scene);
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant& value);
public slots:
void scrollBarMoved();
private:
QGraphicsProxyWidget* proxy;
QTableView* m_view;
QScrollBar* scrollbar;
bool m_isScrollMoving;
};
//SeekBar.cpp
#include <QBrush>
#include <QGraphicsScene>
#include "seekbar.h"
SeekBar::SeekBar(int width, QTableView* view, QGraphicsScene* scene) :
QGraphicsRectItem(nullptr),
proxy(new QGraphicsProxyWidget()),
m_view(view),
m_isScrollMoving(false)
{
proxy->setWidget(m_view);
scene->addItem(proxy);
setParentItem(proxy);
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
setBrush(Qt::red);
setRect(0, 0, width, m_view->height());
scrollbar = m_view->horizontalScrollBar();
connect(m_view->horizontalScrollBar(), &QScrollBar::sliderMoved, this, &SeekBar::scrollBarMoved);
}
QVariant SeekBar::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant& value)
{
if (change == QGraphicsItem::ItemPositionChange && !m_isScrollMoving)
{
QPointF p = value.toPointF();
qreal max = parentItem()->boundingRect().right() - boundingRect().right();
qreal min = parentItem()->boundingRect().left() - boundingRect().left();
if (p.x() > max)
{
p.setX(max);
}
else if (p.x() < min)
{
p.setX(min);
}
p.setY(pos().y());
float percentage = (p.x() - min) * 1.0 / (max - min);
int value = scrollbar->minimum() + percentage * (scrollbar->maximum() - scrollbar->minimum());
scrollbar->setValue(value);
return p;
}
else if (change == QGraphicsItem::ItemPositionChange && m_isScrollMoving)
{
QPointF p = value.toPointF();
qreal max = parentItem()->boundingRect().right() - boundingRect().right();
qreal min = parentItem()->boundingRect().left() - boundingRect().left();
setVisible(true);
if (p.x() > max)
{
hide();
}
else if (p.x() < min)
{
hide();
}
p.setY(pos().y());
return p;
}
return QGraphicsRectItem::itemChange(change, value);
}
void SeekBar::scrollBarMoved()
{
m_isScrollMoving = true;
int col = m_view->columnAt(pos().x());
setPos(m_view->columnViewportPosition(col), 0);
m_isScrollMoving = false;
}
//TableView.h
#include <QTableView>
class TableView : public QTableView
{
public:
TableView(QWidget* parent = nullptr);
};
//TableView.cpp
#include "tableview.h"
#include <QHeaderView>
#include <QStandardItemModel>
#include <QScrollBar>
TableView::TableView(QWidget* parent) : QTableView(parent)
{
QStandardItemModel* model = new QStandardItemModel(10, 10);
setModel(model);
verticalHeader()->hide();
horizontalHeader()->setHighlightSections(false);
setEditTriggers(QAbstractItemView::NoEditTriggers);
setSelectionMode(QAbstractItemView::MultiSelection);
setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
}
//main.cpp
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsProxyWidget>
#include "tableview.h"
#include "seekbar.h"
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
QGraphicsView view;
QGraphicsScene* scene = new QGraphicsScene;
view.setScene(scene);
TableView* table = new TableView;
SeekBar* seekBarItem = new SeekBar(5, table, scene);
view.resize(640, 480);
view.show();
return a.exec();
}
What do i want:
I want to smoothly scroll the table view whenever i drag the seek bar using mouse but it should stay inside the view's boundaries.
I want to keep the seek at a fixed position(column) while scrolling using the horizontal bar. But it should NOT have any boundaries i.e. while using scrollbar, the seek should be able to pass through the view or hide but should appear at the fixed position when scrolled back.
Seek at column 5
Horizontal bar scrolled a little bit to the right
Scrolled till the end of the view
Scrolled back to column 5
EDIT1:
For instance, if the seek is at column 5, while scrolling using horizontal scroll bar it should stick to column 5 and should pass through the boundaries(or hide it when it hits the boundaries?). Seek should be visible whenever the column 5 is in the view while scrolling.
I would appreciate some ideas.
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__