I have a QGraphicsItem for which I want to animate a size change. Because of this, I need to vary both the height and width of the object over time. I have used the QTimeLine in the past for single-variable animations and I would like to use it here for two-variable if possible. However, I don't see a QTimeLine::setFrameRange() that works for two variables.
How can I accomplish this? Is there a better Qt class for this?
Since animations have to be attached to objects, your graphics item will be a QGraphicsObject, so you can expose the relevant properties to the Qt property system.
Since you're animating what amounts to a QSizeF, the simplest way would be to expose this as a single property and use a QPropertyAnimation.
In general, if you have to animate multiple properties in parallel, set them up as individual QPropertyAnimations. Then run those in parallel using a QParallelAnimationGroup.
It's of course possible to use a QTimeLine, but then you have to interpolate between the endpoints manually, basing on the frame number, for example. Why bother.
Below is a complete example that shows how to animate three properties at once: pos, size and rotation.
main.cpp
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QPropertyAnimation>
#include <QParallelAnimationGroup>
#include <QSequentialAnimationGroup>
#include <QGraphicsObject>
#include <QPainter>
#include <QApplication>
class Item : public QGraphicsObject {
Q_OBJECT
qreal m_pen;
QSizeF m_size;
Q_PROPERTY(QSizeF size READ size WRITE setSize NOTIFY newSize)
Q_SIGNAL void newSize();
qreal width() const { return m_size.width() + m_pen; }
qreal height() const { return m_size.height() + m_pen; }
public:
explicit Item(QGraphicsItem *parent = 0) :
QGraphicsObject(parent), m_pen(5.0), m_size(50.0, 50.0) {}
QSizeF size() const { return m_size; }
void setSize(const QSizeF & size) {
if (m_size != size) {
m_size = size;
update();
emit newSize();
}
}
QRectF boundingRect() const {
return QRectF(-width()/2, -height()/2, width(), height());
}
void paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *) {
p->setPen(QPen(Qt::black, m_pen));
p->drawEllipse(QPointF(0,0), width()/2, height()/2);
p->setPen(QPen(Qt::red, m_pen));
p->drawLine(0, 0, 0, height()/2);
}
};
void animate(QObject * obj)
{
QParallelAnimationGroup * group = new QParallelAnimationGroup(obj);
QPropertyAnimation * pos = new QPropertyAnimation(obj, "pos", obj);
QPropertyAnimation * size = new QPropertyAnimation(obj, "size", obj);
QPropertyAnimation * rot= new QPropertyAnimation(obj, "rotation", obj);
pos->setDuration(3000);
pos->setLoopCount(-1);
pos->setEasingCurve(QEasingCurve::InOutCubic);
pos->setStartValue(QPointF(-50, -50));
pos->setEndValue(QPointF(50, 50));
size->setDuration(1500);
size->setLoopCount(-1);
size->setEasingCurve(QEasingCurve::InOutElastic);
size->setStartValue(QSizeF(100, 100));
size->setEndValue(QSizeF(100, 30));
rot->setDuration(1000);
rot->setLoopCount(-1);
rot->setStartValue(0.0);
rot->setEndValue(360.0);
group->addAnimation(pos);
group->addAnimation(size);
group->addAnimation(rot);
group->start();
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene s;
QGraphicsView v(&s);
Item * item = new Item;
s.addItem(item);
v.setRenderHint(QPainter::Antialiasing);
v.setSceneRect(-125, -125, 300, 300);
v.show();
animate(item);
return a.exec();
}
#include "main.moc"
Related
I have a custom version of QGraphicsPolygonItem which is defined here:
#ifndef CUSTOMGPOLYGON_H
#define CUSTOMGPOLYGON_H
#include <QObject>
#include <QGraphicsPolygonItem>
#include <string>
#include <QGraphicsSceneMouseEvent>
#include <QMenu>
#include <QGraphicsTextItem>
class CustomGPolygon : public QObject, public QGraphicsPolygonItem
{
Q_OBJECT
public:
CustomGPolygon(QPolygonF poly, QObject *parent);
~CustomGPolygon();
using QGraphicsPolygonItem::boundingRect;
using QGraphicsPolygonItem::paint;
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
QGraphicsTextItem *className;
private slots:
void deletePolygon();
void copyPolygon();
signals:
void duplicatePoly(QPolygonF);
private:
QMenu menu;
};
#endif // CUSTOMGPOLYGON_H
This is the .cpp for my CustomGPolygon:
#include "customgpolygon.h"
#include <iostream>
CustomGPolygon::CustomGPolygon(QPolygonF poly, QObject *parent):QGraphicsPolygonItem(poly)
{
menu.addAction("Copy", this, SLOT(copyPolygon()));
menu.addAction("Delete", this, SLOT(deletePolygon()));
connect(this, SIGNAL(duplicatePoly(QPolygonF)), parent, SLOT(drawPolygon(QPolygonF)));
}
CustomGPolygon::~CustomGPolygon()
{}
void CustomGPolygon::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(event->buttons() & Qt::LeftButton)
{
QGraphicsItem::mouseMoveEvent(event);
QPolygonF poly = this->polygon();
QPointF edgePoint(0,0);
for(int i = 0; i<poly.size(); i++){
if(poly.at(i).x() > edgePoint.x() && poly.at(i).y() > edgePoint.y())
{
edgePoint.setX(poly.at(i).x());
edgePoint.setY(poly.at(i).y());
}
}
this->className->setPos(edgePoint);
}
}
void CustomGPolygon::deletePolygon()
{
delete this;
}
void CustomGPolygon::copyPolygon()
{
QPolygonF poly = this->polygon();
emit duplicatePoly(poly);
}
To Draw one of these polygons onto my QGraphicsScene, I use the following function in my mainwindow.cpp:
void MainWindow::drawPolygon(const QPolygonF &poly)
{
CustomGPolygon *objectPt = new CustomGPolygon(poly, this);
objectPt->setPen(pen);
objectPt->setFlag(QGraphicsItem::ItemIsMovable);
scene->addItem(objectPt);
objectPt->className = textItem;
map->drawing = false;
}
When I drag this drawn polygon I need the co-ordinates of the vectors within the boundingRect to update - which at the moment, they are not doing.
I have tried adding these flags to solve the problem:
objectPt->setFlag(QGraphicsItem::ItemSendsGeometryChanges);
objectPt->setFlag(QGraphicsItem::ItemSendsScenePositionChanges);
However the problem remained
The QPolygonF set in the item is not about the coordinates of the scene but about the coordinates of the item, so moving the item will not change the QPolygonF. It is similar to the position of our face: If roads move with respect to the world but not with respect to ourselves. So if you want to get the polygon with respect to the scene you will have to make a conversion using the mapToScene() method. On the other hand if you want to track the position of the item then you should not use mouseMoveEvent() but itemChange().
On the other hand your calculation of the point is incorrect, what you should compare is the distance based on some metric, for example the Euclidean distance, since for example with your logic if the polygon is in position with negative coordinates then the edgePoint will always be ( 0,0).
Considering the above, the solution is:
#include <QtWidgets>
class CustomGPolygon: public QObject, public QGraphicsPolygonItem{
Q_OBJECT
public:
CustomGPolygon(QPolygonF poly, QObject *parent=nullptr):
QObject(parent), QGraphicsPolygonItem(poly), className(nullptr){
setFlag(QGraphicsItem::ItemIsMovable);
setFlag(QGraphicsItem::ItemSendsGeometryChanges);
menu.addAction("Copy", this, &CustomGPolygon::copyPolygon);
menu.addAction("Delete", this, &CustomGPolygon::deletePolygon);
// if(parent)
// connect(this, &CustomGPolygon:: SIGNAL(duplicatePoly(QPolygonF)), parent, SLOT(drawPolygon(QPolygonF)));
}
~CustomGPolygon(){}
QGraphicsTextItem *getClassName() const{return className;}
void setClassName(QGraphicsTextItem *value){className = value;}
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value){
if(change == GraphicsItemChange::ItemPositionChange && !polygon().isEmpty()){
QPolygonF p = mapToScene(polygon());
QPointF edgePoint = *std::max_element(p.begin(), p.end(),
[](const QPointF & x, const QPointF & y) -> bool
{
return QVector2D(x).length() > QVector2D(y).length();
});
if(className)
className->setPos(edgePoint);
}
return QGraphicsPolygonItem::itemChange(change, value);
}
private Q_SLOTS:
void deletePolygon(){delete this;}
void copyPolygon(){
QPolygonF poly = mapToScene(polygon());
Q_EMIT duplicatePoly(poly);
}
Q_SIGNALS:
void duplicatePoly(QPolygonF);
private:
QGraphicsTextItem *className;
QMenu menu;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
QGraphicsView view(&scene);
QPolygonF poly;
poly << QPointF(0, 0) << QPointF(100, 0) << QPointF(100, 100);
CustomGPolygon *item = new CustomGPolygon(poly);
QGraphicsTextItem *textItem = new QGraphicsTextItem("Stack Overflow");
scene.addItem(textItem);
scene.addItem(item);
item->setClassName(textItem);
view.show();
view.resize(640, 480);
return a.exec();
}
#include "main.moc"
(warning crossposted on: https://forum.qt.io/topic/105158/qgraphicsscene-item-is-drawn-at-twice-x2-position)
In the following code I am creating a custom widget which has a subclass of QGraphicsScene embeded in it. My aim is to click and add a point to the scene. A point is my subclass of QGraphicsItem (GIPoint). I want to move that point around and later connect it to other points and make a spline path.
The problem I am facing is that the point is not drawn at where I click but at a place which is formed by doubling the mouse-event's scenePos() coordinates. So if I click at (100,100) the point is drawn at (200,200). I suspect that I have misunderstood the coordinate system despite reading the documentation.
The question How to add item in a QGraphicsScene? seems relevant but the proposed solution to transform the mouse-event's coordinates via mapToScene(event->pos()); actually doubles up the position (before it will print that it draws on same position but it would then be x2. Now it also prints it as x2).
So I am asking additionally to point me to some simple-to-digest advice on how the widgets placement works. btw. is QRectF GIPoint::boundingRect() const {
return QRectF(pos().x(), pos().y(), 5, 5);
correct regarding the (x,y) coordinates of the rectangle?
My example code follows:
/* use the following pro:
QT += widgets core gui
CONFIG += debug console
SOURCES = example.cpp
TARGET = example
*/
#include <QGraphicsItem>
#include <QPainter>
#include <QWidget>
#include <QRectF>
#include <QPointF>
#include <QGraphicsScene>
#include <QStyleOptionGraphicsItem>
#include <QGraphicsSceneMouseEvent>
#include <QKeyEvent>
#include <QGraphicsView>
#include <QApplication>
#include <QOpenGLWidget>
#include <QMainWindow>
#include <QGridLayout>
#include <QDebug>
class GIPoint : public QGraphicsItem{
public:
GIPoint(QGraphicsItem * parent, const QPointF &position);
protected:
QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
QRectF boundingRect() const override;
void paint(
QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget
);
};
class GraphicsSceneWidget : public QGraphicsScene {
public:
explicit GraphicsSceneWidget(QObject *parent);
~GraphicsSceneWidget();
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event);
};
class VectorGraphicsWidget : public QWidget {
public:
VectorGraphicsWidget(QWidget *parent);
~VectorGraphicsWidget();
private:
GraphicsSceneWidget *myGraphicsSceneWidget;
};
// implementation
GIPoint::GIPoint(
QGraphicsItem *parent,
const QPointF &position
) : QGraphicsItem(parent) {
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
setPos(position);
qWarning() << "GIPoint::GIPoint() : init at " << position;
}
QVariant GIPoint::itemChange(
GraphicsItemChange change,
const QVariant &value
){
if (change == QGraphicsItem::ItemPositionChange) {
qWarning("position changed");
}
return value;
}
QRectF GIPoint::boundingRect() const {
return QRectF(pos().x(), pos().y(), 5, 5);
// return QRectF(0,0, 5, 5);
}
void GIPoint::paint(
QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget
){
(void )option;
(void )widget;
QPointF xx = scenePos();
QRectF rect = QRectF(xx.x(), xx.y(), 10, 10);
qWarning() << "painting: scenePos " << scenePos() << ", rect " << rect;
QBrush brush = QBrush(Qt::black, Qt::SolidPattern);
//painter->fillRect(rect, brush);
painter->drawRect(rect);
}
GraphicsSceneWidget::GraphicsSceneWidget(QObject *parent)
: QGraphicsScene(parent)
{}
GraphicsSceneWidget::~GraphicsSceneWidget(){}
void GraphicsSceneWidget::mousePressEvent(
QGraphicsSceneMouseEvent *event
){
GIPoint *gip = new GIPoint(Q_NULLPTR, event->scenePos());
addItem(gip);
QGraphicsScene::mousePressEvent(event);
}
VectorGraphicsWidget::VectorGraphicsWidget(QWidget *parent) :
QWidget(parent)
{
myGraphicsSceneWidget = new GraphicsSceneWidget(this);
QGraphicsView *view = new QGraphicsView(myGraphicsSceneWidget);
myGraphicsSceneWidget->setSceneRect(QRectF(0, 0, 500, 500));
QGridLayout *centralLayout = new QGridLayout;
centralLayout->addWidget(view);
setLayout(centralLayout);
myGraphicsSceneWidget->addRect(
QRectF(0, 0, 100, 100),
QPen(Qt::black),
QBrush(Qt::green)
);
view->show();
}
VectorGraphicsWidget::~VectorGraphicsWidget() {
delete myGraphicsSceneWidget;
}
int main(int argc, char **argv){
QApplication app(argc, argv);
app.setApplicationName("test");
app.setOrganizationName("myorg");
app.setOrganizationDomain("myorg.com");
QMainWindow *w = new QMainWindow();
w->resize(500, 500);
w->setCentralWidget(new VectorGraphicsWidget(Q_NULLPTR));
w->show();
return app.exec();
}
The problem is that the boundingRect() and the paint() methods is respect the coordinate system of the item, not the scene. So the solution is not to use scenePos() in both methods but 0, 0:
QRectF GIPoint::boundingRect() const {
return QRectF(0, 0, 10, 10);
}
void GIPoint::paint(
QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget
){
(void )option;
(void )widget;
QRectF rect = QRectF(0, 0, 10, 10);
qWarning() << "painting: scenePos " << scenePos() << ", rect " << rect;
QBrush brush = QBrush(Qt::black, Qt::SolidPattern);
painter->fillRect(rect, brush);
painter->drawRect(rect);
}
Although I would recommend using QGraphicsRectItem as it implements what you have done.
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 am trying to add a glow effect to a QLabel so that it looks like the time display in the following picture:
I found out that you can "misuse" a QGraphicsDropShadowEffect for this:
QGraphicsDropShadowEffect * dse = new QGraphicsDropShadowEffect();
dse->setBlurRadius(10);
dse->setOffset(0);
dse->setColor(QColor(255, 255, 255));
ui.label->setGraphicsEffect(dse);
However, the resulting effect is too weak, you can barely see it:
Unfortunately, you can not modify the strength of the effect, only color and blur radius.
One idea would be to apply multiple QGraphicsDropShadowEffect to the label, so that it gets more visible due to overlapping. But calling ui.label->setGraphicsEffect(dse); will always delete any previous effects, i.e. I was not able to set multiple QGraphicsEffect to the same object.
Any ideas how you can create a clearly visible glow effect with Qt?
Meanwhile, I tinkered my own graphics effect based on QGraphicsBlurEffect and using parts of this answer. If you know any better solutions, let me know.
qgraphicsgloweffect.h:
#pragma once
#include <QGraphicsEffect>
#include <QGraphicsBlurEffect>
#include <QGraphicsColorizeEffect>
#include <QGraphicsPixmapItem>
#include <QGraphicsScene>
#include <QPainter>
class QGraphicsGlowEffect :
public QGraphicsEffect
{
public:
explicit QGraphicsGlowEffect(QObject *parent = 0);
QRectF boundingRectFor(const QRectF &rect) const;
void setColor(QColor value);
void setStrength(int value);
void setBlurRadius(qreal value);
QColor color() const;
int strength() const;
qreal blurRadius() const;
protected:
void draw(QPainter* painter);
private:
static QPixmap applyEffectToPixmap(QPixmap src, QGraphicsEffect *effect, int extent);
int _extent = 5;
QColor _color = QColor(255, 255, 255);
int _strength = 3;
qreal _blurRadius = 5.0;
};
qgraphicsgloweffect.cpp:
#include "QGraphicsGlowEffect.h"
#include <QtCore\qmath.h>
QGraphicsGlowEffect::QGraphicsGlowEffect(QObject *parent) : QGraphicsEffect(parent)
{
}
void QGraphicsGlowEffect::setColor(QColor value) {
_color = value;
}
void QGraphicsGlowEffect::setStrength(int value) {
_strength = value;
}
void QGraphicsGlowEffect::setBlurRadius(qreal value) {
_blurRadius = value;
_extent = qCeil(value);
updateBoundingRect();
}
QColor QGraphicsGlowEffect::color() const {
return _color;
}
int QGraphicsGlowEffect::strength() const {
return _strength;
}
qreal QGraphicsGlowEffect::blurRadius() const {
return _blurRadius;
}
QRectF QGraphicsGlowEffect::boundingRectFor(const QRectF &rect) const {
return QRect(
rect.left() - _extent,
rect.top() - _extent,
rect.width() + 2 * _extent,
rect.height() + 2 * _extent);
}
void QGraphicsGlowEffect::draw(QPainter* painter) {
QPoint offset;
QPixmap source = sourcePixmap(Qt::LogicalCoordinates, &offset);
QPixmap glow;
QGraphicsColorizeEffect *colorize = new QGraphicsColorizeEffect;
colorize->setColor(_color);
colorize->setStrength(1);
glow = applyEffectToPixmap(source, colorize, 0);
QGraphicsBlurEffect *blur = new QGraphicsBlurEffect;
blur->setBlurRadius(_blurRadius);
glow = applyEffectToPixmap(glow, blur, _extent);
for (int i = 0; i < _strength; i++)
painter->drawPixmap(offset - QPoint(_extent, _extent), glow);
drawSource(painter);
}
QPixmap QGraphicsGlowEffect::applyEffectToPixmap(
QPixmap src, QGraphicsEffect *effect, int extent)
{
if (src.isNull()) return QPixmap();
if (!effect) return src;
QGraphicsScene scene;
QGraphicsPixmapItem item;
item.setPixmap(src);
item.setGraphicsEffect(effect);
scene.addItem(&item);
QSize size = src.size() + QSize(extent * 2, extent * 2);
QPixmap res(size.width(), size.height());
res.fill(Qt::transparent);
QPainter ptr(&res);
scene.render(&ptr, QRectF(), QRectF(-extent, -extent, size.width(), size.height()));
return res;
}
Then you can use it like:
QGraphicsGlowEffect * glow = new QGraphicsGlowEffect();
glow->setStrength(4);
glow->setBlurRadius(7);
ui.label->setGraphicsEffect(glow);
This results in a nice glow effect:
I want to make my widget always have square size. Following this answer, I have overridden QWidget::heightForWidth(), and I also call setHeightForWidth(true) in the constructor, as suggested by #peppe. The size policy is set to Preferred,Preferred (for both horizontal size and vertical size).
However, heightForWidth() is not being called. Is there anything I am doing wrong?
This is the declaration of heightForWidth() in my Widget class:
virtual int heightForWidth(int) const;
This happens on Linux and Windows.
Your widget needs to be in a layout. The below works on both Qt 4 and 5.
In Qt 4, it will only force the toplevel window's minimum size if it's in a layout.
In Qt 5, it doesn't force the toplevel window size. There may be a flag for that or it's a bug but I don't recall at the moment.
#include <QApplication>
#include <QWidget>
#include <QPainter>
#include <QDebug>
#include <QVBoxLayout>
#include <QFrame>
class Widget : public QWidget {
mutable int m_ctr;
public:
Widget(QWidget *parent = 0) : QWidget(parent), m_ctr(0) {
QSizePolicy p(sizePolicy());
p.setHeightForWidth(true);
setSizePolicy(p);
}
int heightForWidth(int width) const {
m_ctr ++;
QApplication::postEvent(const_cast<Widget*>(this), new QEvent(QEvent::UpdateRequest));
return qMax(width*2, 100);
}
QSize sizeHint() const {
return QSize(300, heightForWidth(300));
}
void paintEvent(QPaintEvent *) {
QPainter p(this);
p.drawRect(rect().adjusted(0, 0, -1, -1));
p.drawText(rect(), QString("h4w called %1 times").arg(m_ctr));
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QVBoxLayout * l = new QVBoxLayout(&w);
l->addWidget(new Widget);
QFrame * btm = new QFrame;
btm->setFrameShape(QFrame::Panel);
btm->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
l->addWidget(btm);
w.show();
return a.exec();
}
To stay square if the widget is in a layout you must reimplement
bool hasHeightForWidth() const{ return true; }
int heightForWidth(int w) const { return w; }
functions of the layout class.