My code is about images. It can be open image, changing quality, resize, showing image size... For resize and change quaity, I use slider and when I change sliders values, image is read from buffer again and again. Because of this, freezing is happening in my program. So, to solve the problem I want to use QtConcurrent::run and QThread or QFuture. Actually I have no idea how can I use them and I would like to your help to solve my problem.
Here is my code. The functions are that to cause freezing:
void MainWindow::reprocess_image(int scale, int quality) {
rescale_image(scale);
requality_image(quality);
show_pixmap();
}
void MainWindow::rescale_image(int scale) {
int w = m_image->width();
int h = m_image->height();
int new_w = (w * scale)/100;
int new_h = (h * scale)/100;
ui->lbl_width->setText(QString::number(new_w));
ui->lbl_height->setText(QString::number(new_h));
m_pixmap = QPixmap::fromImage(
m_image->scaled(new_w, new_h, Qt::KeepAspectRatio, Qt::FastTransformation));
ui->lbl_scale->setText(QString::number(scale));
}
void MainWindow::requality_image(int quality) {
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
m_pixmap.save(&buffer, "WEBP", quality);
auto l_size_b = buffer.size();
double l_size_kb = buffer.size() / 1024.00;
ui->lbl_size->setText(QString::number(l_size_kb));
QImage image;
image.loadFromData(ba);
m_pixmap = QPixmap::fromImage(image);
ui->lbl_quality->setText(QString::number(quality));
double comp_p = 100.0 * l_size_b / m_orig_size;
if(comp_p>100) {
ui->lbl_compression->setText(QString::number(comp_p));
QLabel* m_label = ui->lbl_size;
m_label->setStyleSheet("QLabel { background-color : red; color : black; }");
}
else if(comp_p<=100) {
ui->lbl_compression->setText(QString::number(comp_p));
QLabel* m_label = ui->lbl_size;
m_label->setStyleSheet("QLabel { background-color : rgba(0,0,0,0%); color : black; }");
}
}
void MainWindow::on_sld_quality_valueChanged(int value) {
reprocess_image(ui->sld_scale->value(), value);
}
void MainWindow::on_sld_scale_valueChanged(int scale) {
reprocess_image(scale, ui->sld_quality->value());
}
And this is my mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QGraphicsPixmapItem>
QT_FORWARD_DECLARE_CLASS(QGraphicsScene)
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
protected:
virtual void showEvent(QShowEvent *e) override;
private slots:
void on_openButton_clicked();
void on_sld_quality_valueChanged(int value);
void on_sld_scale_valueChanged(int value);
void on_saveButton_clicked();
private:
void reprocess_image(int scale, int quality);
void rescale_image(int);
void requality_image(int);
void show_pixmap();
void change_size();
Ui::MainWindow *ui;
QPixmap m_pixmap;
QImage *m_image;
qint64 m_orig_size;
QGraphicsScene *m_scene;
};
#endif // MAINWINDOW_H
How can I integrate QtConcurrent::run(), QThread and QFuture to my code?
The whole point of QtConcurrent::run is that you're not managing your own threads. So there's nothing to integrate. To get notified about when code submitted to QtConcurrent::run has finished you can use QFutureWatcher or emit a signal from the callable.
Related
.h
class myButton : public QPushButton
{
Q_OBJECT
public:
QPropertyAnimation* anim;
struct WidgetPos { int x = 0; int y = 0; int w = 0; int h = 0; };
WidgetPos wp;
void CreateAnimation(QByteArray propertyName)
{
if (propertyName == "geometry")
{
anim = new QPropertyAnimation(this, propertyName);
this->anim->setDuration(100);
this->anim->setEasingCurve(QEasingCurve::Linear);
this->wp.x = this->x();
this->wp.y = this->y();
this->wp.w = this->width();
this->wp.h = this->height();
}
}
myButton(QWidget* parent = 0) : QPushButton(parent) {}
bool eventFilter(QObject* obj, QEvent* event)
{
if (event->type() == QEvent::Enter)
{
if (!this->wp.x)
this->CreateAnimation("geometry");
this->anim->stop();
this->anim->setStartValue(
QRect(this->x(), this->y(), this->width(), this->height()));
this->anim->setEndValue(
QRect(this->x(), this->y(), (this->wp.w + 200) - this->width(), this->height()));
this->anim->start();
}
else if (event->type() == QEvent::Leave)
{
this->anim->stop();
this->anim->setStartValue(
QRect(this->x(), this->y(), (this->wp.w + 200) - this->width(), this->height()));
this->anim->setEndValue(
QRect(this->wp.x, this->wp.x, this->wp.w, this->wp.h));
this->anim->start();
}
return QWidget::eventFilter(obj, event);
}
};
.cpp
QtWidgetsApplication::QtWidgetsApplication(QWidget * parent)
: QMainWindow(parent)
{
ui.setupUi(this);
QPushButton* btn = new myButton(this);
btn->setGeometry(100, 100, 50, 40);
btn->setStyleSheet(R"(QPushButton {
background-image: url(:/tutorial.png);
background-repeat: no-repeat; }
)");
QLabel* labl = new QLabel(btn);
labl->setObjectName("label");
labl->setGeometry(32, 0, btn->width() + 32, btn->height());
labl->setText("Hello World");
labl->setAlignment(Qt::AlignCenter);
labl->show();
btn->installEventFilter(btn);
return;
}
So far what I did result on:
If I move the mouse on it so fast it becomes messy, and the "closing" animation <= isn't working.
I'm struggling with the calculation of the animation QRect and handling it when there's an animation already running.
The goal is to create a smooth animation effect similar to see in this gif:
I think the reason for the issue you are having is because when you are leaving the widget you set the start animation to the maximum width the button could take instead of starting it from the current width. I've implemented my own QPushButton subclass in the following way which seems to achieve the result you need. Instead of creating an event filter, I'll just override the enter and leave event. We'll also need to update the initial geometry every time the widget is moved or resized (outside of the animation), so I'm overriding the move and resize event as well.
// MyButton.h
class MyButton : public QPushButton
{
public:
MyButton(QWidget* parent = nullptr);
~MyButton() = default;
protected:
void enterEvent(QEvent *event) override;
void leaveEvent(QEvent* event) override;
void moveEvent(QMoveEvent *event) override;
void resizeEvent(QResizeEvent* event) override;
private:
QPropertyAnimation* m_animation;
QRect m_init_geometry;
double m_duration;
double m_extension;
};
Here is the implementation:
// MyButton.cpp
MyButton::MyButton(QWidget* parent)
: QPushButton(parent)
, m_animation(nullptr)
, m_init_geometry()
, m_duration(200)
, m_extension(100)
{
m_animation = new QPropertyAnimation(this, "geometry", this);
m_animation->setDuration(m_duration);
m_animation->setEasingCurve(QEasingCurve::Linear);
m_init_geometry = geometry();
}
void MyButton::enterEvent(QEvent *event)
{
QPushButton::enterEvent(event);
m_animation->stop();
// update the duration so that we get a uniform speed when triggering this animation midway
m_animation->setDuration(((m_init_geometry.width() + m_extension - width())/m_extension)*m_duration);
m_animation->setStartValue(geometry());
m_animation->setEndValue(QRectF(m_init_geometry.x(), m_init_geometry.y(), m_init_geometry.width() + m_extension, m_init_geometry.height()));
m_animation->start();
}
void MyButton::leaveEvent(QEvent *event)
{
QPushButton::leaveEvent(event);
m_animation->stop();
// update the duration so that we get a uniform speed when triggering this animation midway
m_animation->setDuration(((width() - m_init_geometry.width())/m_extension)*m_duration);
m_animation->setStartValue(geometry());
m_animation->setEndValue(m_init_geometry);
m_animation->start();
}
void MyButton::moveEvent(QMoveEvent *event)
{
// ignore the move event if it's due to the animation, otherwise store the new geometry
if(m_animation->state() == QPropertyAnimation::Running) return;
QPushButton::moveEvent(event);
m_init_geometry.setTopLeft(event->pos());
}
void MyButton::resizeEvent(QResizeEvent *event)
{
// ignore the move event if it's due to the animation, otherwise store the new geometry
if(m_animation->state() == QPropertyAnimation::Running) return;
QPushButton::resizeEvent(event);
m_init_geometry.setSize(event->size());
}
Notice that the start value of the closing animation is the current geometry and not the initial geometry plus the extended width. I'm updating reducing the duration of the opening animation linearly depending on how close the current width is to the full extended width; similarly for the closing animation. The rest now is very similar to your code:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
auto* btn = new MyButton(this);
btn->setGeometry(100, 100, 60, 80);
btn->setStyleSheet(R"(QPushButton {
background-image: url(:/ubuntu.png);
background-repeat: no-repeat;
background-origin: content;
background-position: left center;}
)");
auto* labl = new QLabel("Hello World", btn);
labl->setAlignment(Qt::AlignCenter);
labl->setGeometry(btn->width(), 0, labl->width(), btn->height());
}
The result looks like this
I try this way:
in mybutton.h
#ifndef MYBUTTON_H
#define MYBUTTON_H
#include <QLabel>
#include <QPushButton>
class MyButton : public QPushButton
{
Q_OBJECT
public:
MyButton(QWidget* parent = nullptr);
// QObject interface
public:
bool eventFilter(QObject *watched, QEvent *event);
signals:
void mouseEnter();
void mouseLeave();
private:
};
#endif // MYBUTTON_H
in mybutton.cpp
#include "mybutton.h"
#include <QEvent>
#include <QLabel>
MyButton::MyButton(QWidget *parent):
QPushButton(parent)
{
}
bool MyButton::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::HoverEnter)
{
emit mouseEnter();
}
else if (event->type() == QEvent::HoverLeave)
{
emit mouseLeave();
}
}
I use signal and in MainWindow class UI I add widget and layouts:
in mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLabel>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
QLabel *labl;
QLabel *labl2;
};
#endif // MAINWINDOW_H
in mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <mybutton.h>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
MyButton* btn = new MyButton(this);
btn->setObjectName("button 1 ");
btn->setText("btn 1");
btn->setGeometry(ui->widget->x(), 100, 50, 50);
ui->widget->layout()->addWidget(btn);
// QLabel
labl= new QLabel(btn,Qt::ToolTip);
connect(btn,&MyButton::mouseEnter,this,[btn,this](){
labl->setObjectName("label");
labl->setGeometry(btn->x()+this->x()+btn->width()+10, btn->y()+this->y()+btn->height()+15,
labl->width(), labl->height());
labl->setText("Hello World");
labl->setAlignment(Qt::AlignCenter);
labl->show();
});
connect(btn,&MyButton::mouseLeave,this,[this](){
labl->hide();
});
btn->installEventFilter(btn);
MyButton* btn2 = new MyButton(this);
btn2->setObjectName("button 2 ");
btn2->setText("btn 2");
btn2->setGeometry(ui->widget->x(), 100, 50, 50);
ui->widget->layout()->addWidget(btn2);
// QLabel
labl2= new QLabel(btn2,Qt::ToolTip);
connect(btn2,&MyButton::mouseEnter,this,[btn2,this](){
labl2->setObjectName("label");
labl2->setGeometry(btn2->x()+this->x()+btn2->width()+10, btn2->y()+this->y()+btn2->height()+15,
labl2->width(), labl2->height());
labl2->setText("Hello World 2");
labl2->setAlignment(Qt::AlignCenter);
labl2->show();
});
connect(btn2,&MyButton::mouseLeave,this,[this](){
labl2->hide();
});
btn->installEventFilter(btn);
btn2->installEventFilter(btn2);
}
MainWindow::~MainWindow()
{
delete ui;
}
and this is my result:
By using the same color in the stylesheet you can have what you show in your Gif.
You can try this approach:
Calculate the start and end rect only once and set in the animation object.
On enter you start the animation as before.
On leave you can change the direction of the animation from play forward to backward.
You can run the animation backwards with
this->anim->setDirection( QAbstractAnimation::Backward );
I don't know if you must surround it with
this->anim->pause();
//[...]
this->anim->resume();
Maybe you must experience with it a little.
Also, maybe you must keep track of
1.) Did you start a animation in forward and backward already for not start it twice or even more often, e.g. use an enum to safe state in a member:
enum class eState { Stopped, Forward, Backward };
2.) Test if the animation is still running or finished already for eventually start a new animation in either forward or backward direction, e.g. test with
this->anim->currentTime() < this->anim->totalDuration();
// or just query the state
this->anim->state() == QAbstractAnimation::Stopped;
I hope I could help you to solve your problem.
EDIT My point 1.) you can also solve with the methods of the animation class by testing:
this->anim->direction(); // and...
this->anim->state();
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'm quite new on qt. I'm trying to create a tab on my ui that can give the option to count how many usb cameras are connected to computer and list then using OPENCV. After that, I want to select one of then and start streaming a video into a custom widget, using paintEvent. My main difficulties are: how to start and stop a streaming when a button is pressed. Below i'm posting my code.
mainwindow.h
//includes...
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void on_CheckCamerasButton_clicked();
void on_StartStreamingButton_clicked();
private:
Ui::MainWindow *ui;
cameraimage camera;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow(){
delete ui;
}
void MainWindow::on_StartStreamingButton_clicked(){
camera.startStreaming();
}
void MainWindow::on_CheckCamerasButton_clicked(){
camera.stopStreaming();
}
cameraimage.h
//includes...
class cameraimage : public QWidget
{
Q_OBJECT
public:
explicit cameraimage(QWidget *parent = nullptr);
private:
QPoint mPoint;
QTimer *timer;
cv::VideoCapture captureVideo;
public slots:
void paintEvent(QPaintEvent * event);
void startStreaming();
void stopStreaming();
};
#endif // CAMERAIMAGE_H
cameraimage.cpp
#include "cameraimage.h"
cameraimage::cameraimage(QWidget *parent) : QWidget(parent)
{
setMouseTracking(true);
}
void cameraimage::startStreaming(){
qDebug() << "Starting Streaming";
captureVideo.open(-1);
if (captureVideo.isOpened() == false){
qDebug() << "Camera can't open";
return;
}
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start(1);
}
void cameraimage::stopStreaming(){
captureVideo.release();
timer->stop();
}
void cameraimage::paintEvent(QPaintEvent *){
cv::Mat tmpImage;
cv::Mat image;
captureVideo.read(tmpImage);
if (tmpImage.empty() == true){
qDebug() << "EMPTY!";
return;
}
cv::cvtColor(tmpImage, image, CV_BGR2RGB);
QImage img((const unsigned char*)(image.data), image.cols, image.rows, QImage::Format_RGB888);
QPixmap pixmap = QPixmap::fromImage(img);
QPainter painter(this);
float comprimento = 1.0*width()/pixmap.width();
float altura = 1.0*height()/pixmap.height();
float ratio = 0.;
if (comprimento<=altura)
ratio = comprimento;
else
ratio = altura;
QSize size = ratio*pixmap.size();
size.setHeight(size.height()-10);
QPoint p;
p.setX(0 + (width()-size.width())/2);
p.setY(5);
painter.drawPixmap(QRect(p, size), pixmap.scaled(size, Qt::KeepAspectRatio));
}
I've got the following output after clicked on StartStreamingButton on MainWindow:
Starting Streaming...
EMPTY!
EMPTY!
EMPTY!
...
Can someone help me please?
Best regards :D
I had a properly working Qt code with custom widget. Then I refractored a signal name and undo the action immediately. Since then I'm getting -
mainwindow.obj:-1: error: LNK2001: unresolved external symbol "protected: virtual void
__thiscall QWidget::mouseDoubleClicked(class QMouseEvent *)"
?mouseDoubleClicked#QWidget##MAEXPAVQMouseEvent###Z)
mouseDoubleClicked is the method which wmits the refractored signal. Although I undo the refractoring action but still it's giving this error. The same code did run bofe that.
I've tried cleaning and running qMake but no success. Even created a new project and copy pasted the file contents but still got the same error.
These are the files-
mainwindow.h
#pragma once;
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
};
myqtimagewidget.h
#pragma once
#include <QWidget>
#include <QImage>
#include <QPainter>
#include <QtGui/QMouseEvent>
#include "opencv2/opencv.hpp"
class MyQtImageWidget : public QWidget
{
Q_OBJECT
protected:
QImage qimage;
cv::Mat temp;
void paintEvent(QPaintEvent*);
void mouseDoubleClicked(QMouseEvent* event);
// void mousePressEvent(QMouseEvent *event);
public:
explicit MyQtImageWidget(QWidget *parent = 0) : QWidget(parent) {}
QSize sizeHint() const {return qimage.size(); }
QSize minimumSizeHint() const {return qimage.size(); }
signals:
void signalMouseMoved(QString);
public slots:
void showImage(const cv::Mat &image);
};
myqtimagewidget.cpp
#include "myqtimagewidget.h"
#include "opencv2/opencv.hpp"
using namespace cv;
void MyQtImageWidget::showImage(const Mat &image)
{
//resize image to fit widget. Maintains aspect ratio
int width = this->maximumWidth();
int hight = this->maximumHeight();
Mat rescaledImg;
if(image.cols > width || image.rows > hight)
{
// debug info --v
QString data = "Original dimention: " + QString::number(image.cols) + "x" + QString::number(image.rows);
qDebug(data.toStdString().c_str());
// debug info --^
double aspectRatio = (double)image.cols / (double)image.rows; // width / height
cv::Size2i newSize;
// debug info --v
qDebug(std::to_string(width).c_str());
qDebug(std::to_string(hight).c_str());
// debug info --^
if(image.cols >= image.rows)
{
newSize = cv::Size(width, width / aspectRatio);
}
else
{
newSize = cv::Size(hight * aspectRatio, hight);
}
cv::resize(image, rescaledImg, newSize, 1, 1);
// debug info --v
data = "Resized dimention: " + QString::number(rescaledImg.cols) + "x" + QString::number(rescaledImg.rows);
qDebug(data.toStdString().c_str());
// debug info --^
}
else
rescaledImg = image;
// Convert the image to the RGB888 format
switch (rescaledImg.type())
{
case CV_8UC1:
cvtColor(rescaledImg, temp, CV_GRAY2RGB);
break;
case CV_8UC3:
cvtColor(rescaledImg, temp, CV_BGR2RGB);
break;
}
// QImage needs the data to be stored continuously in memory
assert(temp.isContinuous());
// Assign OpenCV's image buffer to the QImage. Note that the bytesPerLine parameter
// (http://qt-project.org/doc/qt-4.8/qimage.html#QImage-6) is 3*width because each pixel
// has three bytes.
qimage = QImage(temp.data, temp.cols, temp.rows, temp.cols*3, QImage::Format_RGB888);
repaint();
}
void MyQtImageWidget::paintEvent(QPaintEvent *event)
{
// Display the image
QPainter painter(this);
painter.drawImage(QPoint(0,0), qimage);
painter.end();
}
void MyQtImageWidget::mouseDoubleClicked(QMouseEvent *event)
{
int x = event->pos().x(), y = event->pos().y();
QString data = "x: " + QString::number(x) + ", y: " + QString::number(y);
emit MyQtImageWidget::signalMouseMoved(data);
}
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "opencv2/opencv.hpp"
#include "myqtimagewidget.h"
using namespace cv;
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QString imageSrc = "E:\\Images\\Panorama\\Harshil3578_stitch.jpg";
Mat image = imread(imageSrc.toStdString(), IMREAD_COLOR);
}
MainWindow::~MainWindow()
{
delete ui;
}
In the GUI I have a widget promoted to MyQtImageWidget and connected to a QLabel.
Consider what the linker is telling you:
QWidget has a virtual method named mouseDoubleClicked.
That method is not implemented.
The problem is that QWidget does not have a virtual method named mouseDoubleClicked. My best guess is that during your refactoring, you somehow modified the header file for QWidget, renaming an existing virtual function to mouseDoubleClicked.
If that's the case, getting a fresh copy of qwidget.h (or re-installing Qt) should resolve the problem.
I want to make a simple QWidget which is a simple rectangle fade away. The main problem is that the paint event paint at the same place every time in fact making the effect opposite, it make the colour stronger. Is there any way of achieving this functionality? Could you maybe provide some simple example?
My code:
`
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTimer>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
void paintEvent(QPaintEvent*);
~Widget();
private:
Ui::Widget *ui;
int alfa;
int i;
QTimer time;
};
#endif // WIDGET_H
`
and the cpp:
#include "widget.h"
#include "ui_widget.h"
#include <QColor>
#include <QPainter>
#include <QBrush>
#include <QTimer>
#include <QDebug>
Widget::Widget(QWidget *parent) :
QWidget(parent,Qt::Popup | Qt::FramelessWindowHint),
ui(new Ui::Widget),
time()
{
ui->setupUi(this);
setAttribute(Qt::WA_NoSystemBackground, true);
setAttribute(Qt::WA_TranslucentBackground, true);
setAttribute(Qt::WA_PaintOnScreen);
time.setInterval(500);
time.start();
connect(&time,SIGNAL(timeout()),this,SLOT(update()));
alfa=100;
i=0;
}
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
int rectBase = height();
QColor c(255,0,255);
alfa=alfa-(i*10);
c.setAlpha(alfa);
qDebug()<<c.alpha();
i++;
painter.setBrush(QBrush(c));
painter.drawRect(0, 0, width(),height());
}
Widget::~Widget()
{
delete ui;
}
You shouldn't rely on QWidget::paintEvent() to change your alpha level, since it can be called less or more than you want (multiple update() calls may result in only one paintEvent() and paintEvent() may be called when you don't expect it).
So a more reliable way to get to the result, is have a separate slot where you decrease the alpha level and then call update(). Your class definition might look like this:
class Widget : public QWidget
{
Q_OBJECT
public:
Widget( QWidget * inParent );
private:
void paintEvent(QPaintEvent *);
private slots:
void animate();
private:
QTimer * mTimer;
int mAlpha;
};
And the declaration:
Widget::Widget( QWidget * inParent )
:
QWidget( inParent ),
mTimer( new QTimer( this ) ),
mAlpha( 255 )
{
setAttribute(Qt::WA_NoSystemBackground, true);
setAttribute(Qt::WA_TranslucentBackground, true);
setAttribute(Qt::WA_PaintOnScreen);
mTimer->setInterval( 40 );
mTimer->setSingleShot( false );
connect( mTimer, SIGNAL(timeout()), this, SLOT(animate()) );
mTimer->start();
}
void
Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setBrush( QColor(255,0,255,mAlpha ) );
painter.drawRect(rect());
}
void
Widget::animate()
{
if ( mAlpha > 0 )
{
mAlpha -= 3;
}
else
{
mTimer->stop();
}
update();
}
Notice that I did decrease the interval of the timer. You only called an update() every half a second. That typically does not result in a smooth animation.
I get a warning with this code on Kubuntu Linux, under Qt5:
QWidget::paintEngine: should no longer be called
The originating line is in qWarning("QWidget::paintEngine: Should no longer be called");, in src/widgets/kernel/qwidget_qpa.cpp and discussed a bit in this ticket:
https://qt.gitorious.org/qt/qtbase-harmattan/commit/3037525
You can get the warning to stop by removing the setAttribute(Qt::WA_PaintOnScreen);, so I did that. After taking that line out, it works for me--although your subtraction model is strange. You are modifying the alpha as well as changing the subtraction value on each iteration; you probably didn't intend both. So either change it to:
QColor c (255, 0, 255);
alfa = alfa - 10;
if (alfa >= 0) {
c.setAlpha(alfa);
} else {
time.stop();
}
...or:
QColor c(255,0,255);
if (alfa - i * 10 >= 0) {
c.setAlpha(alfa - i * 10);
i++;
} else {
time.stop();
}
Etc. (See also #PrisonMonkeys note on your timer not necessarily being the only source of update() calls.) Regarding getting these warnings to be more vocal so you don't miss them, you might look at The Essential Noisy Debug Hook For Qt, which I should update.
If with the change, an alpha blended window doesn't work on your platform at all, you should mention explicitly what your circumstance is...as it is working for me.