The error is:
no matching function for call to 'Game::drawPanel(int, int, int, int, Qt::GlobalColor)'
drawPanel(0,0,150,768,Qt::darkCyan);
^
Im new with Qt and I started making a game. I was following a youtube tutorial and I cant find where is mistake in my code. Its the Opacity problem but on the video, guy didnt provide this argument and it worked.
Game.h
#ifndef GAME_H
#define GAME_H
#include <QGraphicsView>
#include <QGraphicsScene>
#include "Interface.h"
class Game: public QGraphicsView{
Q_OBJECT
public:
// constructors
Game(QWidget* parent=NULL);
// public methods
void displayMainMenu();
QString getWhosTurn();
void setWhosTurn(QString player);
// public attributes
QGraphicsScene* scene;
Interface* interface;
QString* fight; // wskaźnik??
public slots:
void start();
private:
void drawPanel(int x, int y, int width, int height, QColor color, double opacity);
void drawGUI();
QString whosTurn_;
QGraphicsTextItem* whosTurnText;
};
#endif //GAME_H
Game.cpp
#include "Game.h"
#include "Interface.h"
#include "Button.h"
#include <QGraphicsTextItem>
Game::Game (QWidget *parent){
// set up the screen
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setFixedSize(1024,768);
// set up the scene
scene = new QGraphicsScene();
scene->setSceneRect(0,0,1024,768);
setScene(scene);
}
void Game::start(){
// clear the screen
scene->clear();
interface = new Interface();
interface->placeHexes();
}
void Game::drawPanel(int x, int y, int width, int height, QColor color, double opacity){
// draws a panel at the specified location with the specified properties
QGraphicsRectItem* panel = new QGraphicsRectItem(x,y,width,height);
QBrush brush;
brush.setStyle(Qt::SolidPattern);
brush.setColor(color);
panel->setBrush(brush);
panel->setOpacity(opacity);
scene->addItem(panel);
}
void Game::drawGUI(){
// draw the left panel
drawPanel(0,0,150,768,Qt::darkCyan);
// draw the right panel
drawPanel(874,0,150,768,Qt::darkCyan);
// place player1 text
QGraphicsTextItem* p1 = new QGraphicsTextItem("Tekst roboczy 1");
p1->setPos(25,0);
scene->addItem(p1);
// place player2 text
QGraphicsTextItem* p2 = new QGraphicsTextItem("Tekst roboczy 2");
p2->setPos(874+25,0);
scene->addItem(p2);
// place whosTurnText
whosTurnText = new QGraphicsTextItem();
setWhosTurn(QString("PLAYER1"));
whosTurnText->setPos(490,0);
scene->addItem(whosTurnText);
}
void Game::displayMainMenu(){
// create the title text
QGraphicsTextItem* titleText = new QGraphicsTextItem(QString("RockPaperScisors?"));
QFont titleFont("comic sans",50);
titleText->setFont(titleFont);
int txPos = this->width()/2 - titleText->boundingRect().width()/2;
int tyPos = 150;
titleText->setPos(txPos,tyPos);
scene->addItem(titleText);
// create the Multiplayer button
Button* multiplayerButton = new Button(QString("Multiplayer"));
int bxPos = this->width()/2 - multiplayerButton->boundingRect().width()/2;
int byPos = 275;
multiplayerButton->setPos(bxPos,byPos);
connect(multiplayerButton,SIGNAL(clicked()),this,SLOT(start()));
scene->addItem(multiplayerButton);
// create the quit button
Button* quitButton = new Button(QString("Quit"));
int qxPos = this->width()/2 - quitButton->boundingRect().width()/2;
int qyPos = 350;
quitButton->setPos(qxPos,qyPos);
connect(quitButton,SIGNAL(clicked()),this,SLOT(close()));
scene->addItem(quitButton);
}
QString Game::getWhosTurn(){
return whosTurn_;
}
void Game::setWhosTurn(QString player){
// change the QString
whosTurn_ = player;
// change the QGraphicsTextItem
whosTurnText->setPlainText(QString("Whos turn: ") + player);
}
Related
I am trying to make my own reusable button class in SFML. This would allow me to create a button and add a callback function to it in order to make the creation of buttons much easier.
Here is my hpp file:
#ifndef Button_hpp
#define Button_hpp
#include <stdio.h>
#include <SFML/Graphics.hpp>
#include "View.hpp"
#include "State.hpp"
#include "Window.hpp"
namespace kge {
class Button: public View{
private:
sf::RectangleShape* _buttonOutline;
sf::RenderWindow* _window;
sf::Clock _clock;
std::string _textString;
sf::Text* _text;
public:
Button(Window*, std::string);
~Button();
virtual void update(float td);
std::function<void(void)> callback;
void setPosition(float x, float y);
};
}
#endif /* Button_hpp */
And here is where I generate the buttons:
_restartButton = new kge::Button(_window, "Restart");
_restartButton->setPosition(getCenterOfScreen().x-((11*fontSize)/2), 300);
_restartButton->callback = ([this](){
State::instance().currentView = new GameView(this->_window);
this->_window->setView(State::instance().currentView);
});
_exitButton = new kge::Button(_window, "Quit");
_exitButton->setPosition(getCenterOfScreen().x-((11*fontSize)/2), 500);
_exitButton->callback = ([this](){
this->_window->close();
});
Finally, I tell the button to update and do it's checks in my window update, button->update(td)
All my buttons seem to all do the action of the last set callback. In this case, my restart button executes my exit code.
Why is this happening and how would I fix it?
Edit
Here is my generation code:
#ifndef GameOver_hpp
#define GameOver_hpp
#include <stdio.h>
#include "View.hpp"
#include "Button.hpp"
#include "GameView.hpp"
#include "State.hpp"
namespace kge{
class View;
};
class GameOver: public kge::View{
private:
sf::Text* _text;
kge::Button* _restartButton;
kge::Button* _exitButton;
void restartFunction(void){
}
public:
GameOver(kge::Window* screen) : View(screen){
int fontSize = 50;
_text = new sf::Text();
_text->setFont(kge::AssetManager::mainBundle().getFontNamed("mainfont"));
_text->setString("Game Over");
_text->setCharacterSize(fontSize);
_text->setPosition(getCenterOfScreen().x-((9*fontSize)/2), 100);
_text->setFillColor(sf::Color(255,0,0));
_restartButton = new kge::Button(_window, "Restart");
_restartButton->setPosition(getCenterOfScreen().x-((11*fontSize)/2), 300);
_exitButton = new kge::Button(_window, "Quit");
_exitButton->setPosition(getCenterOfScreen().x-((11*fontSize)/2), 500);
_restartButton->callback = ([this](){
// State::instance().currentView = new GameView(this->_window);
// this->_window->setView(State::instance().currentView);
puts("Restart");
});
_exitButton->callback = ([this](){
// this->_window->close();
puts("Restart");
});
this->addItemToView(_text);
this->addItemToView(_restartButton);
this->addItemToView(_exitButton);
}
void update(float td){
_restartButton->update(td);
_exitButton->update(td);
}
~GameOver(){
delete _text;
delete _restartButton;
}
};
#endif /* GameOver_hpp */
Note, kge::View is just a custom sf::Drawable class (how I create my own "views")
Edit 2
Button update function:
void Button::update(float td){
if(_clock.getElapsedTime().asMilliseconds() < 400) return;
if(!sf::Mouse::isButtonPressed(sf::Mouse::Left)) return;
if(mouseIsIn(*_buttonOutline, _window)){
callback();
_clock.restart();
}
}
Please note: _clock is an sf::Clock that is stored privately in the button class.
The issue was resolved in chat. To summarize, the mouseIsIn function that #iProgram posted did not check collision correctly, leading to multiple buttons triggering at the same time.
The original function:
bool mouseIsIn(sf::RectangleShape shape, sf::RenderWindow* window){
sf::FloatRect shapeBounds = shape.getGlobalBounds();
sf::Vector2i mousePos = sf::Mouse::getPosition(*window);
sf::FloatRect mouseRect = sf::FloatRect(mousePos.x, mousePos.y, mousePos.x+shapeBounds.width, mousePos.y+shapeBounds.height);
return mouseRect.intersects(shapeBounds);
}
The fixed function:
bool mouseIsIn(sf::RectangleShape shape, sf::RenderWindow* window){
sf::FloatRect shapeBounds = shape.getGlobalBounds();
sf::Vector2i mousePos = sf::Mouse::getPosition(*window);
return shapeBounds.contains(mousePos.x, mousePos.y);
}
I'm doing a school project and I'm stuck here. I'm trying to make my hexagon gradually change its color from yellow to blue. This is my hexagon.hh:
#ifndef HEXAGON_HH
#define HEXAGON_HH
#include <QGraphicsPolygonItem>
#include <QPropertyAnimation>
#include "gamecontroller.hh"
class GameController;
class Hexagon : public QGraphicsPolygonItem
{
public:
Hexagon(QGraphicsItem *parent = 0);
~Hexagon();
GameController* _controller;
Common::CubeCoordinate _coord;
protected:
void mousePressEvent(QGraphicsSceneMouseEvent* event);
};
#endif // HEXAGON_HH
I tried to use QPropertyAnimation like this:
QPropertyAnimation* animation = new QPropertyAnimation(_Tilemap.at(tileCoord)->hexagon_, "brush");
animation->setDuration(10000);
animation->setStartValue(QBrush(Qt::yellow()));
animation->setEndValue(QBrush(Qt::blue()));
animation->start();
But it couldn't be used on the hexagon class so it didn't work. How could I make the hexagon change its color so that there's an animation?
e: heres the error I get when I tried to use QPropertyAnimation:
/home/litmanen/test/UI/gamecontroller.cpp:256: error: no matching function for call to ?QPropertyAnimation::QPropertyAnimation(Hexagon*&, const char [6])?
QPropertyAnimation* animation = new QPropertyAnimation(_Tilemap.at(tileCoord)->hexagon_, "brush");
The error is caused because QPropertyAnimation only apply to QObject, in your case QGraphicsPolygonItem is not. So a possible solution is to inherit from QObject:
*.h
#ifndef HEXAGON_H
#define HEXAGON_H
#include <QBrush>
#include <QGraphicsPolygonItem>
#include <QObject>
class Hexagon : public QObject, public QGraphicsPolygonItem
{
Q_OBJECT
Q_PROPERTY(QBrush brush READ brush WRITE setBrush)
public:
explicit Hexagon(QObject *parent=nullptr);
GameController* _controller;
Common::CubeCoordinate _coord;
protected:
void mousePressEvent(QGraphicsSceneMouseEvent* event);
};
#endif // HEXAGON_H
*.cpp
#include "hexagon.h"
Hexagon::Hexagon(QObject *parent):
QObject(parent)
{
/*another code*/
}
void Hexagon::mousePressEvent(QGraphicsSceneMouseEvent* event){
/*another code*/
}
On the other hand it still does not work since there is no interpolator for QBrush, so the solution is to implement an interpolator (use the interpolator of this solution)
static QVariant brushInterpolator(const QBrush &start, const QBrush &end, qreal progress)
{
QColor cstart = start.color();
QColor cend = end.color();
int sh = cstart.hsvHue();
int eh = cend.hsvHue();
int ss = cstart.hsvSaturation();
int es = cend.hsvSaturation();
int sv = cstart.value();
int ev = cend.value();
int hr = qAbs( sh - eh );
int sr = qAbs( ss - es );
int vr = qAbs( sv - ev );
int dirh = sh > eh ? -1 : 1;
int dirs = ss > es ? -1 : 1;
int dirv = sv > ev ? -1 : 1;
return QBrush(QColor::fromHsv( sh + dirh * progress * hr,
ss + dirs * progress * sr,
sv + dirv * progress * vr), progress > 0.5 ? Qt::SolidPattern : Qt::Dense6Pattern );
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
qRegisterAnimationInterpolator<QBrush>(brushInterpolator);
// ...
Another solution is to implement the same logic with QColor that does not need an interpolator:
*.h
#ifndef HEXAGON_H
#define HEXAGON_H
#include <QGraphicsPolygonItem>
#include <QObject>
class Hexagon : public QObject, public QGraphicsPolygonItem
{
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor)
public:
explicit Hexagon(QObject *parent=nullptr);
QColor color() const;
void setColor(const QColor &color);
GameController* _controller;
Common::CubeCoordinate _coord;
protected:
void mousePressEvent(QGraphicsSceneMouseEvent* event);
};
#endif // HEXAGON_H
*.cpp
#include "hexagon.h"
#include <QBrush>
Hexagon::Hexagon(QObject *parent):
QObject(parent)
{
QBrush b = brush();
b.setStyle(Qt::SolidPattern);
setBrush(b);
/*another code*/
}
QColor Hexagon::color() const
{
return brush().color();
}
void Hexagon::setColor(const QColor &color)
{
QBrush b = brush();
b.setColor(color);
setBrush(b);
}
void Hexagon::mousePressEvent(QGraphicsSceneMouseEvent* event){
/*another code*/
}
Then you use "color" instead of "brush":
QPropertyAnimation* animation = new QPropertyAnimation(_Tilemap.at(tileCoord)->hexagon_, "color");
animation->setDuration(10000);
animation->setStartValue(QColor(Qt::yellow));
animation->setEndValue(QColor(Qt::blue));
animation->start();
Another simpler solution is to use QVariantAnimation:
auto it = _Tilemap.at(tileCoord)->hexagon_;
QVariantAnimation *animation = new QVariantAnimation;
QObject::connect(animation, &QVariantAnimation::valueChanged, [it](const QVariant & v){
it->setBrush(QBrush(v.value<QColor>()));
});
animation->setDuration(10000);
animation->setStartValue(QColor(Qt::yellow));
animation->setEndValue(QColor(Qt::blue));
animation->start();
I'm trying to create and save QGraphicsScene to .svg file and my code works properly. Which creates rectangle,circle and save it into .svg But problem is which can only open rectangles only,how can I implement this to open circle(ellipse too) Please help me.
here are my codes.
readsvg.h
#ifndef READSVG_H
#define READSVG_H
#include <QList>
#include <QGraphicsRectItem>
class ReadSVG
{
public:
ReadSVG();
static QList<QGraphicsRectItem *> getElements(const QString filename);
static QRectF getSizes(const QString filename);
//static QList<QGraphicsEllipseItem *> getElements(const QString filename);
//static QRectF getSizes(const QString filename);
};
#endif // READSVG_H
readsvg.cpp
#include "readsvg.h"
#include <QPen>
#include <QFile>
#include <QMessageBox>
#include <QDomDocument>
#include <QStringList>
ReadSVG::ReadSVG()
{
}
QList<QGraphicsRectItem *> ReadSVG::getElements(const QString filename)
{
QList<QGraphicsRectItem *> rectList; // We declare in the stack a list of rectangles
QDomDocument doc; // document object
QFile file(filename); // Open our SVG file
// If it did not open or could not transfer content to QDocDocument
if (!file.open(QIODevice::ReadOnly) || !doc.setContent(&file))
return rectList; // then return the list, but empty
// Look in the document for all objects with the tag g
QDomNodeList gList = doc.elementsByTagName("g");
// We start to sort them out
for (int i = 0; i < gList.size(); i++) {
QDomNode gNode = gList.item(i); // Select the node from the list
QDomElement rectangle = gNode.firstChildElement("rect"); // And we search in it for an element with the tag rect
// If the resulting elements are not zero, then
if (rectangle.isNull()){
continue;
} else {
// begin to form a rectangle
QGraphicsRectItem *rect = new QGraphicsRectItem();
// This flag makes the object moveable, it will be required for verification
rect->setFlag(QGraphicsItem::ItemIsMovable);
// We take sizes from the rect tag
QDomElement gElement = gNode.toElement();
rect->setRect(rectangle.attribute("x").toInt(),
rectangle.attribute("y").toInt(),
rectangle.attribute("width").toInt(),
rectangle.attribute("height").toInt());
/*
We take the parameters of the colors gNode from the node element
yes yes yes ... it's from gNode, not from rectangle. These parameters are stored in the tag g
* */
QColor fillColor(gElement.attribute("fill", "#ffffff")); // fill color
fillColor.setAlphaF(gElement.attribute("fill-opacity","0").toFloat());
rect->setBrush(QBrush(fillColor));
// as well as the color and thickness of the outline
QColor strokeColor(gElement.attribute("stroke", "#000000"));
strokeColor.setAlphaF(gElement.attribute("stroke-opacity").toFloat());
rect->setPen(QPen(strokeColor,gElement.attribute("stroke-width", "0").toInt()));
rectList.append(rect); // add a rectangle to the list
}
}
file.close();
return rectList;
}
QRectF ReadSVG::getSizes(const QString filename)
{
QDomDocument doc; // initialize the QDomDocument object on the stack
QFile file(filename); // Open our SVG file
// If it did not open or could not transfer content to QDocDocument
if (!file.open(QIODevice::ReadOnly) || !doc.setContent(&file))
return QRectF(0,0,200,200); // then return the values for the default scene
/* Next, we take the list of elements with the tag svg.
* In case the list of elements is not empty,
* then we will take the dimensions of the graphic scene.
* */
QDomNodeList list = doc.elementsByTagName("svg");
if(list.length() > 0) {
QDomElement svgElement = list.item(0).toElement();
QStringList parameters = svgElement.attribute("viewBox").split(" ");
return QRectF(parameters.at(0).toInt(),
parameters.at(1).toInt(),
parameters.at(2).toInt(),
parameters.at(3).toInt());
}
return QRectF(0,0,200,200);
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QWidget>
#include <QtCore>
#include <QtGui>
#include <QSvgGenerator>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private slots:
void on_pushButton_clicked();
void on_btnSave_clicked();
void on_btnOpen_clicked();
private:
Ui::Widget *ui;
QGraphicsScene *scene;
QGraphicsEllipseItem *elipse;
QGraphicsRectItem *rect;
QString path;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include "readsvg.h"
#include <QCursor>
#include <QFileDialog>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
scene = new QGraphicsScene(this);
ui->graphicsView->setScene(scene);
QBrush redBrush(Qt::red);
QBrush blueBrush(Qt::blue);
QPen blackPen(Qt::black);
blackPen.setWidth(6);
elipse = scene->addEllipse(10,10,100,100,blackPen,redBrush);
rect = scene->addRect(-10,-10,100,100,blackPen,blueBrush);
rect->setFlag(QGraphicsItem::ItemIsMovable, true);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
QString fileName= QFileDialog::getSaveFileName(this, "Save image", QCoreApplication::applicationDirPath(), "BMP Files (*.bmp);;JPEG (*.JPEG);;PNG (*.png)" );
if (!fileName.isNull())
{
QPixmap pixMap = this->ui->graphicsView->grab();
pixMap.save(fileName);
}
}
void Widget::on_btnSave_clicked()
{
// Заберём путь к файлу и его имененем, который будем создавать
QString newPath = QFileDialog::getSaveFileName(this, trUtf8("Save SVG"),
path, tr("SVG files (*.svg)"));
if (newPath.isEmpty())
return;
path = newPath;
QSvgGenerator generator;
generator.setFileName(path);
generator.setSize(QSize(scene->width(), scene->height()));
generator.setViewBox(QRect(0, 0, scene->width(), scene->height()));
generator.setTitle(trUtf8("SVG Example"));
generator.setDescription(trUtf8("File created by SVG Example"));
QPainter painter;
painter.begin(&generator);
scene->render(&painter);
painter.end();
}
void Widget::on_btnOpen_clicked()
{
QString newPath = QFileDialog::getOpenFileName(this, trUtf8("Open SVG"),
path, tr("SVG files (*.svg)"));
if (newPath.isEmpty())
return;
path = newPath;
scene->clear();
scene->setSceneRect(ReadSVG::getSizes(path)); // Set the size of the graphic scene
// Install the objects on the graphical scene, get them using the getElements
foreach (QGraphicsRectItem *item, ReadSVG::getElements(path)) {
QGraphicsRectItem *rect = item;
scene->addItem(rect);
}
}
You can continue with the approach that you have started. Look for elements with the tag names that you want. Start with the "circle" tag. Here's what a ciricle tag might look like;
<circle cx="5" cy="5" r="3" fill="#0f0"/>
I'm trying to create a simple frame in Qt with a tick and some text. I made two new label implementations because I wanted the labels to dynamically fill all the available space but when I resize the window the sizes are off, as shown by the qDebug output, which represents the size of the image label:
Resized: 244 , 244 <-- Window first created
Resized: 305 , 305 <-- Window maximized
Resized: 135 , 135 <-- Window restored to original size
As you can see, when the window is restored to its original size the image is not. The last size should be 244, 244.
The code which describes the behaviour of the two widgets is the following:
"widgets.h":
/*
* This file includes many custom widgets.
*/
#ifndef APOCRYPHA_WIDGETS
#define APOCRYPHA_WIDGETS
#include <QWidget>
#include <QLabel>
#include <QTimer>
#include <QPixmap>
#include <QResizeEvent>
#include <QPaintEvent>
class AutoTextLabel : public QLabel {
Q_OBJECT
public:
explicit AutoTextLabel(QWidget* parent);
AutoTextLabel(QWidget* parent, QString text);
protected:
void resizeEvent(QResizeEvent* event) override;
private:
QTimer* resizeTimer;
private slots:
void onResizeEnd();
};
class AutoImageLabel : public QLabel {
Q_OBJECT
public:
explicit AutoImageLabel(QWidget* parent);
AutoImageLabel(QWidget* parent, const QPixmap& pixmap);
void setFillOrientation(int orientation);
QSize sizeHint() const override;
public slots:
void setPixmap(const QPixmap &newPix);
void resizeEvent(QResizeEvent* event) override;
protected:
// void paintEvent(QPaintEvent* event) override;
private:
int fillOrientation;
int widthForHeight(int h) const;
int heightForWidth(int w) const override;
QPixmap scaledPixmap() const;
QPixmap labelPixmap;
};
#endif //APOCRYPHA_WIDGETS
"widgets.cpp":
/*
* This file includes many custom widgets.
*/
#include "widgets.h"
#include <QPainter>
#include <QDebug>
AutoTextLabel::AutoTextLabel(QWidget *parent, QString text) : QLabel(text, parent){
// Enable antialiasing
QFont aaFont(font());
aaFont.setStyleStrategy(QFont::PreferAntialias);
setFont(aaFont);
// This timer is used to fire a slot when a window is resized
resizeTimer = new QTimer();
resizeTimer->setSingleShot(true);
connect(resizeTimer, SIGNAL(timeout()), SLOT(onResizeEnd()));
}
AutoTextLabel::AutoTextLabel(QWidget *parent) : AutoTextLabel(parent, "") {}
void AutoTextLabel::resizeEvent(QResizeEvent *event) {
QWidget::resizeEvent(event);
// Only fire when 25ms have passed since the last resize.
resizeTimer->start(25);
}
void AutoTextLabel::onResizeEnd() {
QFont updatedFont(font());
// Resize Text
if (!text().isEmpty()){
int fontSize = 1;
updatedFont.setPixelSize(fontSize);
QRect boundingRectangle;
// Update bounding rectangle
if (wordWrap())
boundingRectangle = QFontMetrics(updatedFont).boundingRect(contentsRect(), Qt::TextWordWrap, text());
else
boundingRectangle = QFontMetrics(updatedFont).boundingRect(text());
while (boundingRectangle.height() <= contentsRect().height()) {
fontSize++;
updatedFont.setPixelSize(fontSize);
// Update bounding rectangle
if (wordWrap())
boundingRectangle = QFontMetrics(updatedFont).boundingRect(contentsRect(), Qt::TextWordWrap, text());
else
boundingRectangle = QFontMetrics(updatedFont).boundingRect(text());
}
updatedFont.setPixelSize(fontSize - 1);
setFont(updatedFont);
}
}
/* Auto Image Label */
AutoImageLabel::AutoImageLabel(QWidget *parent, const QPixmap &pixmap) : QLabel(parent) {
setMinimumSize(1, 1);
setScaledContents(false);
setPixmap(pixmap);
}
AutoImageLabel::AutoImageLabel(QWidget *parent) : QLabel(parent) {
setScaledContents(false);
}
void AutoImageLabel::resizeEvent(QResizeEvent *event) {
QWidget::resizeEvent(event);
if(!labelPixmap.isNull())
QLabel::setPixmap(scaledPixmap());
qDebug() << "Resized: " << scaledPixmap().width() << ", " << scaledPixmap().height();
}
int AutoImageLabel::widthForHeight(int h) const {
return labelPixmap.isNull() ? width() : (labelPixmap.width() * h) / labelPixmap.height();
}
int AutoImageLabel::heightForWidth(int w) const {
return labelPixmap.isNull() ? height() : (labelPixmap.height() * w) / labelPixmap.width();
}
void AutoImageLabel::setFillOrientation(int orientation) {
this->fillOrientation = orientation;
}
QSize AutoImageLabel::sizeHint() const {
if (fillOrientation == Qt::Horizontal)
return QSize(width(), heightForWidth(width()));
else
return QSize(widthForHeight(height()), height());
}
QPixmap AutoImageLabel::scaledPixmap() const {
return labelPixmap.scaled(sizeHint(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
void AutoImageLabel::setPixmap(const QPixmap &newPix) {
labelPixmap = newPix;
QLabel::setPixmap(scaledPixmap());
}
"other_frames.h":
//
// Created by Riccardo on 18/09/2017.
//
#ifndef APOCRYPHA_OTHER_FRAMES_H
#define APOCRYPHA_OTHER_FRAMES_H
#include <QFrame>
#include <QLabel>
#include <QGridLayout>
#include <QWidget>
#include <QResizeEvent>
#include <QPixmap>
#include <QTimer>
#include "widgets.h"
class ConfirmationFrame : public QFrame {
Q_OBJECT
public:
explicit ConfirmationFrame(QWidget* parent);
ConfirmationFrame(QWidget* parent, const QString& text);
private:
QGridLayout* layout;
AutoImageLabel* imageLabel;
AutoTextLabel* textLabel;
};
#endif //APOCRYPHA_OTHER_FRAMES_H
"other_frames.cpp":
//
// Created by Riccardo on 18/09/2017.
//
#include "other_frames.h"
#include <QDebug>
ConfirmationFrame::ConfirmationFrame(QWidget* parent, const QString &text) : QFrame(parent) {
textLabel = new AutoTextLabel(this, text);
QPixmap pix(":/images/check-tick.png");
imageLabel = new AutoImageLabel(this, pix);
textLabel->setAlignment(Qt::AlignCenter);
imageLabel->setAlignment(Qt::AlignCenter);
textLabel->setWordWrap(true);
// Green Background
setStyleSheet("background-color: rgba(106, 242, 94, 1);");
layout = new QGridLayout();
layout->setSpacing(0);
layout->setContentsMargins(32, 32, 32, 32);
layout->setRowStretch(0, 1);
layout->setRowStretch(1, 1);
layout->addWidget(imageLabel, 0, 1);
layout->addWidget(textLabel, 1, 1);
setLayout(layout);
}
ConfirmationFrame::ConfirmationFrame(QWidget *parent) : ConfirmationFrame(parent, "") {
}
"window_main.h":
#ifndef WINDOW_MAIN_H
#define WINDOW_MAIN_H
#include <QMainWindow>
#include <QMenuBar>
#include <QMenu>
#include <QGridLayout>
#include <QFrame>
#include <QScreen>
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
QFrame *mainFrame;
void center(QScreen* screen);
void autoSetSize(QScreen* screen);
private:
void createMenu();
// Components
QGridLayout *mainLayout;
QMenuBar *menuBar;
QMenu *fileMenu;
};
#endif // WINDOW_MAIN
"window_main.cpp":
#include "window_main.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
mainFrame = new QFrame();
mainLayout = new QGridLayout();
mainLayout->setSpacing(0);
mainLayout->setContentsMargins(0, 0, 0, 0);
createMenu();
mainFrame->setStyleSheet("background-color: red;");
mainFrame->setLayout(mainLayout);
setCentralWidget(mainFrame);
}
void MainWindow::createMenu(){
menuBar = new QMenuBar;
fileMenu = new QMenu(tr("&File"), this);
menuBar->addMenu(fileMenu);
setMenuBar(menuBar);
}
void MainWindow::center(QScreen *screen) {
QSize size = screen->availableSize();
int x = size.width() / 2 - width() / 2;
int y = size.height() / 2 - height() / 2;
move(x, y);
}
void MainWindow::autoSetSize(QScreen *screen) {
QSize screenSize = screen->availableSize();
// TODO Math.round
setMinimumSize(QSize((int)(screenSize.width() / 1.25), (int)(screenSize.height() / 1.25)));
}
"main.cpp":
#include <QApplication>
#include <iostream>
#include <QFile>
#include "quiz/choice.h"
#include "quiz/question.h"
#include "quiz/quizmaker.h"
#include <QSettings>
#include <QStandardPaths>
#include <QDebug>
#include <src/user_interface/other_frames.h>
#include "user_interface/window_main.h"
#include <QScreen>
#include <QFontDatabase>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
// Set Application Parameters
QCoreApplication::setOrganizationName("Riccardo Fagiolo");
QCoreApplication::setOrganizationDomain("kopharex.me");
QCoreApplication::setApplicationName("Apocrypha");
// Set application font
const int id = QFontDatabase::addApplicationFont(":/fonts/montserrat/Montserrat-Regular.otf");
QString family = QFontDatabase::applicationFontFamilies(id).at(0);
QFont font(family);
font.setStyleStrategy(QFont::PreferAntialias);
a.setFont(font);
// App Settings
QSettings settings;
settings.setValue("data_dir", QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
// Create UI
auto* window = new MainWindow();
ConfirmationFrame* cframe = new ConfirmationFrame(window, "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?");
window->mainFrame->layout()->addWidget(cframe);
window->autoSetSize(a.primaryScreen());
//cframe->updateTextLabel();
window->show();
window->center(a.primaryScreen());
// [...] - Nothing related to user interface.
return a.exec();
}
Here is a screenshot of the current MainWindow and ConfirmationFrame to give you an idea of what i'm trying to accomplish:
Window Screenshot
All comments regarding the code are welcome.
Thanks for any help,
Riccardo
Hello I tried to fix the resizing issue with an hack.
Before starting the timer to resize the text, just reduce its font to a 1 pixel font:
void AutoTextLabel::resizeEvent(QResizeEvent *event) {
QWidget::resizeEvent(event);
// set a very small font, then start the timer
QFont updatedFont(font());
updatedFont.setPixelSize(1);
setFont(updatedFont);
// Only fire when 25ms have passed since the last resize.
resizeTimer->start(25);
}
Can the effect be acceptable in your opinion?
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__