I try to create a simple application with QGraphicsView. The application can load graphical files with different ratios in the view. I would like to implement the following feature: on the application window resize, the content of the view should be also resized and centered. I do not know this part of the Qt well, so I can resize the content, but I cannot center it. How can I fix it?
MainWindow.h snippet:
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
void resizeEvent(QResizeEvent *event) override;
public slots:
void onButtonClicked();
private:
bool m_flag = false;
QGraphicsPixmapItem *m_item = nullptr;
QGraphicsView *m_view = nullptr;
QPixmap m_pixmap;
};
MainWindow.cpp:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
auto central = new QWidget(this);
setCentralWidget(central);
// layouts
auto mainLayout = new QVBoxLayout;
mainLayout->setAlignment(Qt::AlignTop);
central->setLayout(mainLayout);
// top layout
auto topLayout = new QHBoxLayout;
topLayout->setAlignment(Qt::AlignLeft);
mainLayout->addLayout(topLayout);
auto btn = new QPushButton(this);
btn->setText("Test");
connect(btn, &QPushButton::clicked, this, &MainWindow::onButtonClicked);
topLayout->addWidget(btn);
m_view = new QGraphicsView;
mainLayout->addWidget(m_view);
auto scene = new QGraphicsScene;
m_view->setScene(scene);
m_view->setMinimumSize(800, 600);
m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
QString name = ":/pic1.jpg";
m_pixmap = QPixmap{ name }.scaled(800, 600, Qt::KeepAspectRatio);
m_item = scene->addPixmap(m_pixmap);
m_view->viewport()->resize(m_pixmap.size());
}
void MainWindow::onButtonClicked()
{
m_flag = !m_flag;
QString name = m_flag ? ":/pic2.png" : ":/pic1.jpg";
m_pixmap = QPixmap{ name }.scaled(m_view->size(), Qt::KeepAspectRatio);
m_item->setPixmap(m_pixmap);
m_view->fitInView(m_item, Qt::KeepAspectRatio);
}
void MainWindow::resizeEvent(QResizeEvent *event)
{
QMainWindow::resizeEvent(event);
m_view->fitInView(m_item, Qt::KeepAspectRatio);
}
Test result for the pic2.png:
When an image is scaled, it is done with respect to the top left, so it will always look up if the height is smaller than the prefix or to the right if the width is smaller.
The solution is to repaint the scaled QPixmap in the middle of the final QPixmap.
const QSize pixmap_size{800, 600};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
auto central = new QWidget(this);
setCentralWidget(central);
// layouts
auto mainLayout = new QVBoxLayout;
mainLayout->setAlignment(Qt::AlignTop);
central->setLayout(mainLayout);
// top layout
auto topLayout = new QHBoxLayout;
topLayout->setAlignment(Qt::AlignLeft);
mainLayout->addLayout(topLayout);
auto btn = new QPushButton(this);
btn->setText("Test");
connect(btn, &QPushButton::clicked, this, &MainWindow::onButtonClicked);
topLayout->addWidget(btn);
m_view = new QGraphicsView;
mainLayout->addWidget(m_view);
auto scene = new QGraphicsScene;
m_view->setScene(scene);
m_view->setMinimumSize(pixmap_size);
m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_pixmap = QPixmap(pixmap_size);
m_pixmap.fill(Qt::transparent);
m_item = new QGraphicsPixmapItem;
m_view->scene()->addItem(m_item);
show();
onButtonClicked();
}
QPixmap MainWindow::createPixmap(const QString & filename, const QSize & size) const{
QPixmap tmp = QPixmap{ filename }.scaled(size, Qt::KeepAspectRatio);
QPixmap pixmap(size);
pixmap.fill(Qt::transparent);
QPainter p(&pixmap);
QPoint point(QRect({}, size).center()-tmp.rect().center());
p.drawPixmap(point, tmp);
p.end();
return pixmap;
}
void MainWindow::onButtonClicked()
{
QString name = m_flag ? ":/pic2.png" : ":/pic1.jpg";
m_item->setPixmap(createPixmap(name, pixmap_size));
m_flag = !m_flag;
m_view->fitInView(m_item, Qt::KeepAspectRatio);
}
void MainWindow::resizeEvent(QResizeEvent *event)
{
QMainWindow::resizeEvent(event);
m_view->fitInView(m_item, Qt::KeepAspectRatio);
}
Related
I'm trying to create a simple Image Viewer in Qt with zooming supported.
To display an image file I load it into a QImage and create a QPixmap.
class NN: public QWidget{
Q_OBJECT
Q_DISABLE_COPY(NN)
public:
NN(QWidget* parent = nullptr) : QWidget(parent){
}
const QPixmap& pixmap() const
{
return m_pixmap;
}
void setPixmap(const QPixmap& px)
{
m_pixmap = px;
update();
}
protected:
void paintEvent(QPaintEvent*)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, false);
style()->drawItemPixmap(&painter, rect(), Qt::AlignCenter, m_pixmap.scaled(rect().size()));
}
private:
QPixmap m_pixmap;
};
(This Widget is part of a ScrollArea)
This works fine, but when I try to load large images and zoom in, the performance starts to decrease (lag).
I thought of applying a clip to the drawItemPixmap() method, but I am not quite sure how and whether it would help increasing the performance.
My question is whether the clipping idea would work, and if so how. If not, maybe there is another way to gain performance?
When m_pixmap and/or rect() are very large, the bulk of your slowdown is likely coming from here:
m_pixmap.scaled(rect().size())
Here you are you are asking Qt to create a new QPixmap object the same size as rect(), which is a potentially very expensive operation; and passing that QPixmap object into the call to drawItemPixmap() which will draw just a small portion of the pixmap, after which the QPixmap object will get discarded, and the whole procedure will have to be done again the next time you want to redraw your object.
Needless to say, that can be very inefficient.
A more efficient approach would be to call QPainter::drawPixmap(const QRect & target, const Pixmap & pixmap, const QRect & source), like this:
painter.drawPixmap(rect(), m_pixmap, srcRect);
... and drawPixmap() will draw a scaled pixmap of size rect() (i.e. just the size of your widget) by rescaling the content of m_pixmap that is inside srcRect; much more efficient than rescaling the entire m_pixmap image.
You'll need to calculate the correct left/top/width/height values for srcRect, of course, but that should be straightforward with a little bit of algebra. (Basically just figure out what portion of the pixmap should currently be visible based on your widget's current zoom/pan state)
As I pointed out in this answer, it is better to use QGraphicsView for image scaling so I will translate the code to C++:
#include <QtWidgets>
class ImageViewer: public QGraphicsView{
public:
ImageViewer(QWidget *parent=nullptr):QGraphicsView(parent){
setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
// setAlignment(Qt::AlignLeft | Qt::AlignTop);
setAlignment(Qt::AlignCenter);
setBackgroundRole(QPalette::Dark);
QGraphicsScene *scene = new QGraphicsScene(this);
setScene(scene);
pixmapItem = new QGraphicsPixmapItem;
scene->addItem(pixmapItem);
}
bool setPixmap(const QPixmap & pixmap){
if(pixmap.isNull())
return false;
pixmapItem->setPixmap(pixmap);
return true;
}
void zoom(qreal f){
scale(f, f);
}
void zoomIn(){
zoom(factor);
}
void zoomOut(){
zoom(1.0 / factor);
}
void resetZoom(){
resetTransform();
}
void fitToWindow(){
fitInView(sceneRect(), Qt::KeepAspectRatio);
}
private:
qreal factor = 2.0;
QGraphicsPixmapItem * pixmapItem;
};
class MainWindow: public QMainWindow{
Q_OBJECT
public:
MainWindow(QWidget *parent=nullptr):QMainWindow(parent),
view(new ImageViewer)
{
setCentralWidget(view);
createActions();
createMenus();
resize(640, 480);
}
private Q_SLOTS:
void open(){
QStringList l;
for(const QByteArray & ba: QImageReader::supportedImageFormats()){
l << ("*." + QString::fromUtf8(ba));
}
QString filter = QString("Image Files(%1)").arg(l.join(" "));
QString fileName = QFileDialog::getOpenFileName(
this,
tr("Open Image"),
QDir::currentPath(),
filter
);
if(!fileMenu->isEmpty()){
bool loaded = view->setPixmap(QPixmap(fileName));
fitToWindowAct->setEnabled(loaded);
updateActions();
}
}
void fitToWindow(){
if(fitToWindowAct->isChecked())
view->fitToWindow();
else
view->resetZoom();
updateActions();
}
void about(){
QMessageBox::about(this, "ImageViewer", "ImageViewer");
}
private:
void createActions(){
openAct = new QAction("&Open...", this);
openAct->setShortcut(QKeySequence("Ctrl+O"));
connect(openAct, &QAction::triggered, this, &MainWindow::open);
exitAct = new QAction("E&xit", this);
exitAct->setShortcut(QKeySequence("Ctrl+Q"));
connect(exitAct, &QAction::triggered, this, &MainWindow::close);
zoomInAct = new QAction(tr("Zoom &In (25%)"), this);
zoomInAct->setShortcut(QKeySequence("Ctrl++"));
zoomInAct->setEnabled(false);
connect(zoomInAct, &QAction::triggered, view, &ImageViewer::zoomIn);
zoomOutAct = new QAction(tr("Zoom &Out (25%)"), this);
zoomOutAct->setShortcut(QKeySequence("Ctrl+-"));
zoomOutAct->setEnabled(false);
connect(zoomOutAct, &QAction::triggered, view, &ImageViewer::zoomOut);
normalSizeAct = new QAction(tr("&Normal Size"), this);
normalSizeAct->setShortcut(QKeySequence("Ctrl+S"));
normalSizeAct->setEnabled(false);
connect(normalSizeAct, &QAction::triggered, view, &ImageViewer::resetZoom);
fitToWindowAct = new QAction(tr("&Fit to Window"), this);
fitToWindowAct->setShortcut(QKeySequence("Ctrl+F"));
fitToWindowAct->setEnabled(false);
fitToWindowAct->setCheckable(true);
connect(fitToWindowAct, &QAction::triggered, this, &MainWindow::fitToWindow);
aboutAct = new QAction(tr("&About"), this);
connect(aboutAct, &QAction::triggered, this, &MainWindow::about);
aboutQtAct = new QAction(tr("About &Qt"), this);
connect(aboutQtAct, &QAction::triggered, qApp, &QApplication::aboutQt);
}
void createMenus(){
fileMenu = new QMenu(tr("&File"), this);
fileMenu->addAction(openAct);
fileMenu->addSeparator();
fileMenu->addAction(exitAct);
viewMenu = new QMenu(tr("&View"), this);
viewMenu->addAction(zoomInAct);
viewMenu->addAction(zoomOutAct);
viewMenu->addAction(normalSizeAct);
viewMenu->addSeparator();
viewMenu->addAction(fitToWindowAct);
helpMenu = new QMenu(tr("&Help"), this);
helpMenu->addAction(aboutAct);
helpMenu->addAction(aboutQtAct);
menuBar()->addMenu(fileMenu);
menuBar()->addMenu(viewMenu);
menuBar()->addMenu(helpMenu);
}
void updateActions(){
zoomInAct->setEnabled(not fitToWindowAct->isChecked());
zoomOutAct->setEnabled(not fitToWindowAct->isChecked());
normalSizeAct->setEnabled(not fitToWindowAct->isChecked());
}
ImageViewer *view;
QAction *openAct;
QAction *exitAct;
QAction *zoomInAct;
QAction *zoomOutAct;
QAction *normalSizeAct;
QAction *fitToWindowAct;
QAction *aboutAct;
QAction *aboutQtAct;
QMenu *fileMenu;
QMenu *viewMenu;
QMenu *helpMenu;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
#include "main.moc"
I created a widget class. This class will be maintaining child_1 of type QFrame(which is of type QWidget) and also a child_2 of type QGridLayout . child_2 will be adding child_1 to itself. The child_1 will be having a child of type QGridLayout and ultimately QGridLayout will be having some push buttons.
*I am unable to adjust the size of QFrame child(child_1)as per the adjustment of parent widget.
Also, I am trying to set the size of QFrame child that of parent. Even this isn't happening.
I have tried using methods setFrameRect(), setGeometry() and resize().
I have implemented evenhandler resizeEvent(QResizeEvent event). But while application is running, I am trying to resize the widget and I see that log isn't printing.
main.cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
widget.h
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
QGridLayout* p_mPanelLayout; // Instance of QGridLayout.
FrameDemo* framePtr;
};
widget.cpp
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
p_mPanelLayout = new QGridLayout(this);
framePtr = new FrameDemo(this);
framePtr->createPushButtonUtility();
p_mPanelLayout->addWidget(framePtr, 0, 0, Qt::AlignCenter);
}
Widget::~Widget()
{
delete ui;
}
framedemo.h
#define LOG(message){\
qDebug()<< __LINE__<<__FUNCTION__<<message<<"\n";\
}
class FrameDemo : public QFrame
{
Q_OBJECT
public:
FrameDemo(QWidget *parent = 0);
void createPushButtonUtility();
private:
QGridLayout* m_pLayout;
protected:
void resizeEvent(QResizeEvent * event);
};
framedemo.cpp
FrameDemo::FrameDemo(QWidget *parent):QFrame(parent)
{
m_pLayout = new QGridLayout(this);
// m_pLayout->setRowStretch(0, parent->width());
QRect defaultRect = rect();
QSize currentSize = size();
LOG(currentSize.height())
LOG(currentSize.width())
int xP1 = 0;
int yP1 = 0;
int xP2 = 0;
int yP2 = 0;
defaultRect.getCoords(&xP1, &yP1, &xP2, &yP2);
LOG(xP1)
LOG(yP1)
LOG(xP2)
LOG(yP2)
LOG(parent->width())
LOG(defaultRect.height())
// QRect newRect = QRect(xP1,yP1,parent->width(),defaultRect.height());
// QRect newRect = QRect(0, 0, 0, defaultRect.height());
// setFrameRect(newRect);
resize(QSize(400, currentSize.height()));
setFrameShape(QFrame::Box);
// setGeometry(0, 0, parent->width(), defaultRect.height());
}
void FrameDemo::createPushButtonUtility()
{
QPushButton *readButton = new QPushButton("Read", this);
m_pLayout->addWidget(readButton, 0, 0, Qt::AlignCenter);
QPushButton *writeButton = new QPushButton("Write", this);
m_pLayout->addWidget(writeButton, 0, 1, Qt::AlignCenter);
QPushButton *updateButton = new QPushButton("Update", this);
m_pLayout->addWidget(updateButton, 0, 2, Qt::AlignCenter);
}
void FrameDemo::resizeEvent(QResizeEvent *event)
{
LOG("resize")
}
I managed to fix the problem.
I missed to implement the event handler resizeEvent(QResizeEvent *event) in parent widget.
I implemented the same and called the resizeEvent of FrameDemo. Inside this event handler, I called setGeometry().
I create a chart with QChart & QGraphicsScene & QGraphicsView, but the chart does not auto zoom in or zoom out when window change. How could I do that? I don't there is any signal or slot in QChart, QGraphicsScene, or QGraphicsView class.
I know I could use QChart & QChartView, but I want QChart & QGraphicsScene & QGraphicsView for some purpose. Here is my code for draw a chart:
void MainWindow::on_actionDraw_Sine_Chart_triggered()
{
QSplineSeries *spline = new QSplineSeries;
for (double x = -M_PI; x < M_PI; x += 0.01) {
spline->append(x, sin(x));
}
spline->setName(tr("Sine Curve"));
QChart *chart = new QChart;
chart->addSeries(spline);
chart->createDefaultAxes();
chart->axisX()->setRange(-4, 4);
chart->axisY()->setRange(-1.2, 1.2);
chart->setGeometry(ui->graphicsView->rect());
QGraphicsScene *scene = new QGraphicsScene;
scene->addItem(chart);
ui->graphicsView->setScene(scene);
}
Complete code is available here.
You have to track the size change of the viewport and change the size of the QChart, for that we use eventFilter, but since it is another method you need that chart is an attribute of the class.
In addition to this it is not advisable to create the scene in the one slot, but in the constructor, the same with the QChart, and then only add the series.
*.h
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
...
bool eventFilter(QObject *watched, QEvent *event); // +++
private:
Ui::MainWindow *ui;
QChart *chart; // +++
QGraphicsScene *scene; // +++
};
*.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
scene = new QGraphicsScene;
chart = new QChart;
scene->addItem(chart);
ui->graphicsView->setScene(scene);
ui->graphicsView->viewport()->installEventFilter(this);
}
void MainWindow::on_actionDraw_Sine_Chart_triggered(){
QSplineSeries *spline = new QSplineSeries;
for (double x = -M_PI; x < M_PI; x += 0.01) {
spline->append(x, sin(x));
}
spline->setName(tr("Sine Curve"));
chart->addSeries(spline);
}
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
if(watched == ui->graphicsView->viewport() && event->type() == QEvent::Resize){
if(chart)
chart->resize(ui->graphicsView->viewport()->size());
}
return QMainWindow::eventFilter(watched, event);
}
i've got a problem for my school projet... I just can't get the scene position of any object in a QGraphicsGridLayout, it always give me (0,0). Maybe i subclass badly QGraphicsWidget ?
This is a example of how i use it :
main:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGraphicsScene *scene = new QGraphicsScene();
myCustomWidget *a = new myCustomWidget("a");
myCustomWidget *b = new myCustomWidget("b");
myCustomWidget *c = new myCustomWidget("c");
QGraphicsGridLayout *layout = new QGraphicsGridLayout();
layout->addItem(a,0,0,1,2, Qt::AlignCenter);
layout->addItem(b,1,0,1,1);
layout->addItem(c,1,1,1,1);
QGraphicsWidget *w = new QGraphicsWidget();
w->setLayout(layout);
scene->addItem(w);
QWidget* Window = new QWidget();
Window->setMinimumSize(640,380);
QVBoxLayout* layoutWindow = new QVBoxLayout();
Window->setLayout(layoutWindow);
QGraphicsView *view = new QGraphicsView(Window);
view->setScene(scene);
view->show();
layoutWindow->addWidget(view);
Window->show();
////////////////////////////////
// Always return QPointF(0,0) //
////////////////////////////////
qDebug() << c->scenePos();
return app.exec();
}
myCustomWidget:
class myCustomWidget : public QGraphicsWidget
{
private:
QString caption;
public:
myCustomWidget(QString caption, QGraphicsWidget* parent = 0) : QGraphicsWidget(parent)
{
this->caption = caption;
}
QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
{
Q_UNUSED(which);
Q_UNUSED(constraint);
return QSizeF(100,100);
}
QRectF boundingRect() const
{
return QRectF(QPointF(0,0), QSizeF(100,100));
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget * = 0) Q_DECL_OVERRIDE
{
QFont font;
font.setPixelSize(0.75 * qMin(boundingRect().width(), boundingRect().height()));
painter->fillRect(boundingRect(), Qt::black);
painter->setFont(font);
painter->setPen(QPen(Qt::white));
painter->drawText(boundingRect(), Qt::AlignCenter, caption);
}
};
Say I have a QHBoxLayout where there are 2 QTextEdits and between them a button with an arrow to the right. When you click on the button, the right-side QTextEdit gradually closes by moving the left border until it meets the right one. Simultaneously, the right border of the left QTextEdit takes the place which the right QTextEdit released. And after pressing on the button, the state of the system is coming to the former one.
EDIT: In order to organize this I have done the following:
1) In header file:
class MyWidget : public QWidget
{
Q_OBJECT
QTextEdit *m_textEditor1;
QTextEdit *m_textEditor2;
QPushButton *m_pushButton;
QHBoxLayout *m_layout;
int m_deltaX;
public:
MyWidget(QWidget * parent = 0);
~MyWidget(){}
private slots:
void closeOrOpenTextEdit2(bool isClosing);
};
2) In the source file:
MyWidget::MyWidget(QWidget * parent):QWidget(parent),m_deltaX(0)
{
m_pushButton = new QPushButton(this);
m_pushButton->setText(">");
m_pushButton->setCheckable(true);
connect(m_pushButton, SIGNAL(clicked(bool)), this, SLOT(closeOrOpenTextEdit2(bool)));
m_textEditor1 = new QTextEdit(this);
m_textEditor1->setText("AAAAA AAAAAAAAAAA AAAAAAAAAAA AAAAAAA AAAAAAAAAAA AAAAAAAAAAA AA");
m_textEditor2 = new QTextEdit(this);
m_layout = new QHBoxLayout;
m_layout->addWidget(m_textEditor1);
m_layout->addWidget(m_pushButton);
m_layout->addWidget(m_textEditor2);
setLayout(m_layout);
}
void MyWidget::closeOrOpenTextEdit2(bool isClosing)
{
QPropertyAnimation *animation1 = new QPropertyAnimation(m_textEditor2, "geometry");
QPropertyAnimation *animation2 = new QPropertyAnimation(m_pushButton, "geometry");
QPropertyAnimation *animation3 = new QPropertyAnimation(m_textEditor1, "geometry");
if(isClosing) //close the second textEdit
{
m_pushButton->setText("<");
QRect te2_1 = m_textEditor2->geometry();
m_deltaX = te2_1.width()-3;
QRect te2_2(te2_1.x()+m_deltaX, te2_1.y(), 3 ,te2_1.height());
QRect pb_1 = m_pushButton->geometry();
QRect pb_2(pb_1.x()+m_deltaX, pb_1.y(), pb_1.width() ,pb_1.height());
QRect te1_1 = m_textEditor1->geometry();
QRect te1_2(te1_1.x(), te1_1.y(), te1_1.width()+m_deltaX, te1_1.height());
//animation->setDuration(10000);
animation1->setStartValue(te2_1);
animation1->setEndValue(te2_2);
animation2->setStartValue(pb_1);
animation2->setEndValue(pb_2);
animation3->setStartValue(te1_1);
animation3->setEndValue(te1_2);
}
else //open
{
m_pushButton->setText(">");
QRect te2_1 = m_textEditor2->geometry();
QRect te2_2(te2_1.x()-m_deltaX, te2_1.y(), 3+m_deltaX ,te2_1.height());
QRect pb_1 = m_pushButton->geometry();
QRect pb_2(pb_1.x()-m_deltaX, pb_1.y(), pb_1.width() ,pb_1.height());
QRect te1_1 = m_textEditor1->geometry();
QRect te1_2(te1_1.x(), te1_1.y(), te1_1.width()-m_deltaX, te1_1.height());
//animation->setDuration(10000);
animation1->setStartValue(te2_1);
animation1->setEndValue(te2_2);
animation2->setStartValue(pb_1);
animation2->setEndValue(pb_2);
animation3->setStartValue(te1_1);
animation3->setEndValue(te1_2);
}
animation1->start();
animation2->start();
animation3->start();
}
EDIT:
And I have the following problem:
When I close the second QTextEdit (by clicking on the button) and resize the MyWidget, then the QTextEdit restores its state (but it should stay closed of course). How can I solve this problem?
Please provide me with a code snippet.
Qt's Animation framework sounds like a good place to start. You could just try to follow their tutorials, adapting for you use case. I have used it already, and it seemed quite straight forward.
1) You could replace your button with a vertical layout, place the button inside this layout and finally add a vertical spacer below the button (in same the layout).
...
QVBoxLayout* m_buttonLayout = new QVBoxLayout();
m_layout = new QHBoxLayout();
m_layout->addWidget(m_textEditor1);
m_layout->addLayout(m_buttonLayout);
m_layout->addWidget(m_textEditor2);
m_buttonLayout->addWidget(m_pushButton);
m_buttonLayout->addItem( new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding) );
2) I guess you could (and should) animate widget's maximumSize (or just maximumWidth) property and let the layout take care of calculating actual geometries. This would also simplify your calculations. E.g.
QPropertyAnimation *animation1 = new QPropertyAnimation(m_textEditor2, "maximumWidth");
QPropertyAnimation *animation2 = new QPropertyAnimation(m_textEditor, "maximumWidth");
if (isClosing)
{
int textEdit2_start = m_textEditor2->maximumWidth();
int textEdit2_end = 0;
int textEdit_start = m_textEditor->maximumWidth();
int textEdit_end = textEdit_start + textEdit2_start;
animation1->setStartValue(textEdit2_start);
...
}
Also, now you don't have to animate buttons geometry at all (assuming that you have set fixed size to it).
PS. I didn't compile codes so there might be minor errors but you should get the idea.
Here what I wanted:
Header file
class MyWidget : public QWidget
{
Q_OBJECT
QTextEdit *m_textEditor1;
QTextEdit *m_textEditor2;
QPushButton *m_pushButton;
QHBoxLayout *m_layout;
QVBoxLayout *m_buttonLayout;
int m_deltaX;
bool m_isClosed;
public:
MyWidget(QWidget * parent = 0);
~MyWidget(){}
void resizeEvent( QResizeEvent * event );
private slots:
void closeOrOpenTextEdit2(bool isClosing);
};
Source file
MyWidget::MyWidget(QWidget * parent):QWidget(parent),m_deltaX(0)
{
m_pushButton = new QPushButton(this);
m_pushButton->setText(">");
m_pushButton->setCheckable(true);
m_pushButton->setFixedSize(16,16);
connect(m_pushButton, SIGNAL(clicked(bool)), this, SLOT(closeOrOpenTextEdit2(bool)));
m_textEditor1 = new QTextEdit(this);
m_textEditor1->setText("AAAAA AAAAAAAAAAA AAAAAAAAAAA AAAAAAA AAAAAAAAAAA AAAAAAAAAAA AA");
m_textEditor2 = new QTextEdit(this);
m_buttonLayout = new QVBoxLayout();
m_buttonLayout->addWidget(m_pushButton);
m_buttonLayout->addItem( new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding) );
m_layout = new QHBoxLayout;
m_layout->addWidget(m_textEditor1, 10);
m_layout->addSpacing(15);
m_layout->addLayout(m_buttonLayout);
m_layout->setSpacing(0);
m_layout->addWidget(m_textEditor2, 4);
setLayout(m_layout);
resize(800,500);
}
void MyWidget::closeOrOpenTextEdit2(bool isClosing)
{
m_isClosed = isClosing;
QPropertyAnimation *animation1 = new QPropertyAnimation(m_textEditor2, "maximumWidth");
if(isClosing) //close the second textEdit
{
m_textEditor2->setMaximumWidth(m_textEditor2->width());
int textEdit2_start = m_textEditor2->maximumWidth();
m_deltaX = textEdit2_start;
int textEdit2_end = 3;
animation1->setDuration(500);
animation1->setStartValue(textEdit2_start);
animation1->setEndValue(textEdit2_end);
m_pushButton->setText("<");
}
else //open
{
int textEdit2_start = m_textEditor2->maximumWidth();
int textEdit2_end = m_deltaX;
animation1->setDuration(500);
animation1->setStartValue(textEdit2_start);
animation1->setEndValue(textEdit2_end);
m_pushButton->setText(">");
}
animation1->start();
}
void MyWidget::resizeEvent( QResizeEvent * event )
{
if(!m_isClosed)
m_textEditor2->setMaximumWidth( QWIDGETSIZE_MAX );
}