QDockWidget does not remember floating size and location when IsFloat is toggled - location

QDockWidget has a feature where you can double click on the title bar and the dock will toggle to a floating window and back to its docked state. The problem is if you move and resize the floating window and then toggle back to the dock and then back to floating again, your position and size are lost.
I've looked for solutions to resize and move a QDockWidget and I've taken a look at the Qt source code for QDockWidget. I've created a small subclass of QDockWidget that appears to solve the problem. It overrides the mouseDoubleClick, resize and move events, filtering out the unwanted "random" resizing and positioning by Qt and stores the information about screen, position and size in a struct that I store in QSettings for persistence between sessions.
// header
#include <QDockWidget>
#include "global.h"
class DockWidget : public QDockWidget
{
Q_OBJECT
public:
DockWidget(const QString &title, QWidget *parent = nullptr);
QSize sizeHint() const;
void rpt(QString s);
struct DWLoc {
int screen;
QPoint pos;
QSize size;
};
DWLoc dw;
bool ignore;
protected:
bool event(QEvent *event);
void resizeEvent(QResizeEvent *event);
void moveEvent(QMoveEvent *event);
};
// cpp
#include "dockwidget.h"
DockWidget::DockWidget(const QString &title, QWidget *parent)
: QDockWidget(title, parent)
{
ignore = false;
}
bool DockWidget::event(QEvent *event)
{
if (event->type() == QEvent::MouseButtonDblClick) {
ignore = true;
setFloating(!isFloating());
if (isFloating()) {
// move and size to previous state
QRect screenres = QApplication::desktop()->screenGeometry(dw.screen);
move(QPoint(screenres.x() + dw.pos.x(), screenres.y() + dw.pos.y()));
ignore = false;
adjustSize();
}
ignore = false;
return true;
}
QDockWidget::event(event);
return true;
}
void DockWidget::resizeEvent(QResizeEvent *event)
{
if (ignore) {
return;
}
if (isFloating()) {
dw.screen = QApplication::desktop()->screenNumber(this);
QRect r = geometry();
QRect a = QApplication::desktop()->screen(dw.screen)->geometry();
dw.pos = QPoint(r.x() - a.x(), r.y() - a.y());
dw.size = event->size();
}
}
QSize DockWidget::sizeHint() const
{
return dw.size;
}
void DockWidget::moveEvent(QMoveEvent *event)
{
if (ignore || !isFloating()) return;
dw.screen = QApplication::desktop()->screenNumber(this);
QRect r = geometry();
QRect a = QApplication::desktop()->screen(dw.screen)->geometry();
dw.pos = QPoint(r.x() - a.x(), r.y() - a.y());
dw.size = QSize(r.width(), r.height());
}
While this appears to be working is there a simpler way to accomplish this? What do I do if a QDockWidget was on a screen that is now turned off?

Related

Convert an uploaded from desktop image to black and white in Qt

My program consists of two functions: first a user clicks a button (btn_image) to upload an image from desktop and it displays on the label (lbl_image). Secondly, I push another button (cnv_image) in order to change the colors of that uploaded image to black and white.
I have managed to implement the first function: the image chosen by a user successfully displays. However, I am confused how to convert that image to b&w. I wrote a function that is triggered after clicking the cnv_image button, but the problem is to refer to that uploaded image. So, when I click cnv_image buttom the uploaded image simply disappears.
I tried to use image.load (ui->lbl_image) to refer to the label which contains the image but it shows a mistake.
How can I implement my second function?
void MainWindow::on_btn_image_clicked()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Choose"), "", tr("Images (*.png *.jpg *jpeg)"));
if (QString::compare(fileName, QString()) != 0) {
QImage image;
bool valid = image.load(fileName);
if (valid) {
ui->lbl_image->setPixmap(QPixmap::fromImage(image));
}
}
}
void MainWindow::on_cnv_image_clicked()
{
QImage image;
image.load(ui->lbl_image);
QSize sizeImage = image.size();
int width = sizeImage.width(), height = sizeImage.height();
QRgb color;
for (int f1=0; f1<width; f1++) {
for (int f2=0; f2<height; f2++) {
int gray = qGray(color);
image.setPixel(f1, f2, qRgb(gray, gray, gray));
}
}
ui->lbl_image->setPixmap(QPixmap::fromImage(image));
}
I update your code, I add QImage image; as private member of MainWindow class so In mainwindow.h :
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui
{
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow: public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_btn_image_clicked();
void on_cnv_image_clicked();
private:
Ui::MainWindow *ui;
QImage image;
};
#endif // MAINWINDOW_H
and in on_cnv_image_clicked function
void MainWindow::on_cnv_image_clicked()
{
QSize sizeImage = image.size();
int width = sizeImage.width(), height = sizeImage.height();
QRgb color;
int value;
for (int f1 = 0; f1 < width; f1++)
{
for (int f2 = 0; f2 < height; f2++)
{
color = image.pixel(f1, f2);
int gray = (qRed(color) + qGreen(color) + qBlue(color)) / 3;
image.setPixel(f1, f2, qRgb(gray, gray, gray));
}
}
ui->lbl_image->setPixmap(QPixmap::fromImage(image));
ui->lbl_image->setScaledContents(true);
}
Result :
Welcome to Stackoverflow!
First of all, it's a good idea to keep a copy of the QImage in your class when you load it. It helps to avoid extra conversions from QPixmap to QImage in next steps. I'll skip it because it's out of the scope of your question.
You can use QImage::convertTo to convert the format of a QImage in place. It means that, it does not create a new QImage. As per documentation it may detach the QImage. You can read more about Implicit Sharing if you are interested.
So, the implementation should be something like:
void MainWindow::on_cnv_image_clicked()
{
QImage image = ui->lbl_image->pixmap().toImage();
image.convertTo(QImage::Format_Grayscale8);
ui->lbl_image->setPixmap(QPixmap::fromImage(image));
}
Take a look at the list of QImage::Formats to evaluate other grayscale/mono options.

How to use a Manager class to manage Characters and their animations in C++ using the SFML library

I'm trying to create a 2D sidescroller mini-game. For now, I only have a character with a sprite and one animation, which i'm trying to move using the left/right arrows. At first, I only had a Character class, storing the sprite of the character and its running animation. And it worked. But now, I'm trying to add a CharacterManager class, which will create all the characters to avoid doing it in the main, and which will manage their movements and draw them.
And it doesn't work anymore. I think my problems come from the fact that I have trouble using pointers, which I'm not really familiar with.
Here are the different classes I'm using :
Animation.h :
#pragma once
#include <vector>
#include <SFML/Graphics.hpp>
#include <stdexcept>
#include <ctime>
#include "Constants.h"
class Animation {
public:
Animation();
~Animation();
void SetFrames(std::vector<sf::IntRect> frames) { m_frames = frames; }
sf::IntRect Play();
private:
std::vector<sf::IntRect> m_frames;
unsigned int m_currentFrame;
float m_updateTime;
float m_timeSinceLastFrame;
float m_lastCallTimestamp;
float m_currentTimestamp;
bool m_firstCall;
};
Animation.cpp :
#include "Animation.h"
Animation::Animation() {
m_currentFrame = 0;
m_updateTime = 1.0f / ANIMATION_SPEED;
m_timeSinceLastFrame = 0.0f;
m_firstCall = true;
}
Animation::~Animation() {
}
sf::IntRect Animation::Play() {
if (m_frames.size() == 0) {
throw std::length_error("The frames vector is empty");
}
// Advance time and add the elapsed time to timeSinceLastFrame
m_currentTimestamp = std::clock();
// Ignore elapsed time if first call
if (m_firstCall) {
m_timeSinceLastFrame = 0.0f;
m_lastCallTimestamp = m_currentTimestamp;
m_firstCall = false; // Not first call anymore
}
else {
m_timeSinceLastFrame += (m_currentTimestamp - m_lastCallTimestamp) / CLOCKS_PER_SEC;
m_lastCallTimestamp = m_currentTimestamp;
}
// Next frame
if (m_timeSinceLastFrame >= m_updateTime) {
m_currentFrame++;
m_timeSinceLastFrame = 0;
// Check animation end
if (m_currentFrame >= m_frames.size()) {
m_currentFrame = 0; // Reset frame progression
m_firstCall = true; // Next passage will be the first call of a new animation
/* TODO : return something to alert the end of the animation
(like a specific rectint or set a variable to true and get it on the other side) */
}
}
return m_frames[m_currentFrame];
}
Character.h :
#pragma once
#include<string>
#include<iostream>
#include <SFML/Graphics.hpp>
#include <vector>
#include <map>
#include "Constants.h"
#include "Animation.h"
class Character : public sf::Drawable {
public:
Character();
Character(std::string name);
~Character();
void Move(float value);
// Setters
void SetTexture(std::string filename);
void SetPosition(sf::Vector2f pos) { m_position = pos; };
void SetAnimations(std::map<std::string, Animation*> animations) { m_animations = animations; };
protected:
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const;
std::string m_name;
unsigned int m_orientation; // 0 (default) = right | 1 = left
std::map<std::string, Animation*> m_animations;
Animation runningAnimation;
sf::Vector2f m_position;
sf::Texture m_texture;
sf::Sprite m_sprite;
};
Character.cpp :
#include "Character.h"
Character::Character() {}
Character::Character(std::string name) {
m_name = name;
m_orientation = 0;
runningAnimation = Animation();
}
Character::~Character() {
}
void Character::draw(sf::RenderTarget& target, sf::RenderStates states) const {
target.draw(m_sprite, states);
}
void Character::Move(float value) {
m_sprite.setTextureRect(runningAnimation.Play());
m_position.x += value;
m_sprite.setPosition(m_position);
}
void Character::SetTexture(std::string filename) {
filename = TEXTURE_FILES_PREFIX + filename;
// Load the entire texture file
if (!m_texture.loadFromFile(filename))
std::cout << "Error loading texture file : " << filename << std::endl;
// Set the texture (by default, initialize to idle state) and the position
std::vector<sf::IntRect> runningFrames{
sf::IntRect(67, 45, 19, 28),
sf::IntRect(116, 46, 20, 27),
sf::IntRect(166, 48, 20, 25),
sf::IntRect(217, 45, 22, 28),
sf::IntRect(266, 46, 19, 27),
sf::IntRect(316, 48, 20, 25)
};
runningAnimation.SetFrames(runningFrames);
m_sprite.setTexture(m_texture);
m_sprite.setTextureRect(runningAnimation.Play());
m_sprite.setPosition(m_position);
}
CharacterManager.h :
#pragma once
#include <vector>
#include <map>
#include <iostream>
#include <SFML\Graphics.hpp>
#include "AliveCharacter.h"
#include "Npc.h"
#include "Animation.h"
#include "CharacterStats.h"
enum CharacterType
{
NPC,
ALIVE,
GENERAL
};
// Class containing a vector of character entities and creates the animations of these entities from a data file (later)
class CharacterManager : public sf::Drawable {
public :
CharacterManager();
~CharacterManager();
// Loads the file and stores the content inside data string (not used for now)
void LoadDataFile(std::string filename);
// Create a character and add it to the list
void CreateCharacter(std::string name, std::string textureFilename, CharacterType characterType, sf::Vector2f pos);
void CreateCharacter(std::string name, std::string textureFilename, CharacterType characterType, sf::Vector2f pos, std::map<std::string, Animation*> animations);
void CreateCharacter(std::string name, std::string textureFilename, CharacterType characterType, sf::Vector2f pos, std::map<std::string, Animation*> animations, CharacterStats stats);
void Move(float value);
Character* GetCharacter(std::string name) { return m_characters[name]; }
private :
// Calls the draw() function of each stored Character
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const;
std::string m_data;
std::map<std::string, Character*> m_characters;
};
CharacterManager.cpp :
#include "CharacterManager.h"
CharacterManager::CharacterManager() {
m_characters = std::map<std::string, Character*>();
}
CharacterManager::~CharacterManager() {
//delete m_characters;
}
void CharacterManager::LoadDataFile(std::string filename) {
// TODO : load file content
}
void CharacterManager::CreateCharacter(std::string name, std::string textureFilename, CharacterType characterType, sf::Vector2f pos) {
Character new_character(name); // Create a generic character...
// ... and specialise it depending on the character type param
switch (characterType)
{
case NPC:
new_character = Npc(name);
break;
case ALIVE:
new_character = AliveCharacter(name);
break;
default:
new_character = Character(name);
break;
}
// Set texture, position and add to the characters list
new_character.SetTexture(textureFilename);
new_character.SetPosition(pos);
m_characters.insert({ name, &new_character });
}
void CharacterManager::CreateCharacter(std::string name, std::string textureFilename, CharacterType characterType, sf::Vector2f pos, std::map<std::string, Animation*> animations) {
CreateCharacter(textureFilename, name, characterType, pos);
m_characters[name]->SetAnimations(animations);
}
void CharacterManager::CreateCharacter(std::string name, std::string textureFilename, CharacterType characterType, sf::Vector2f pos, std::map<std::string, Animation*> animations, CharacterStats stats) {
CreateCharacter(textureFilename, name, characterType, pos);
m_characters[name]->SetAnimations(animations);
//m_characters[name]->SetStats(stats);
}
void CharacterManager::Move(float value) {
for each (std::pair<std::string, Character*> pair in m_characters) {
Character* character = pair.second;
character->Move(value);
}
}
void CharacterManager::draw(sf::RenderTarget& target, sf::RenderStates states) const {
for each (std::pair<std::string, Character*> pair in m_characters) {
Character* character = pair.second;
target.draw(*character);
}
}
And finally the Main.cpp, where you can see in comments the things I tried without success :
#include "Map.h"
#include "CharacterManager.h"
int main()
{
sf::RenderWindow window(sf::VideoMode(WINDOW_SIZE_X, WINDOW_SIZE_Y), WINDOW_TITLE);
window.setFramerateLimit(WINDOW_FRAMERATE);
Map map;
int pos = WINDOW_SIZE_X / 2 - MAP_SIZE_X / 2;
float movement = 0;
map.SetPosition(pos);
map.SetGroundTexture("Foreground/Tileset.png");
map.SetBackgroundTexture("Background/BGFront.png");
CharacterManager charManager;
charManager.CreateCharacter("main", "Characters/test-character.png", ALIVE, sf::Vector2f(400, WINDOW_SIZE_Y - HEIGHT_OF_GROUND - 28));
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
if (event.type == sf::Event::KeyPressed)
{
if (event.key.code == sf::Keyboard::Left)
movement = -MOVING_SPEED;
else if (event.key.code == sf::Keyboard::Right)
movement = MOVING_SPEED;
}
else if (event.type == sf::Event::KeyReleased)
movement = 0;
}
// Move the map
map.Scroll(movement);
//charManager.GetCharacter("main")->Move(movement);
charManager.Move(movement);
window.clear();
window.draw(map);
/*Character* mainPerso = charManager.GetCharacter("main");
window.draw(*mainPerso);*/
window.draw(charManager);
window.display();
}
return 0;
}
The error I'm getting is on the return m_frames[m_currentFrame] line in Animation.cpp, in the end of the Play() function. A pop-up window opens saying : "Expression: vector subscript out of range". This error only happens the second time the code goes through this line. The first time it's called from the SetTexture() function of Character.cpp (m_sprite.setTextureRect(runningAnimation.Play())), itself called from the CreateCharacter() function of the CharacterManager (new_character.SetTexture(textureFilename)), and at this point the Animation object looks as it should.
But the second time, it's called from the Move() function of Character (m_sprite.setTextureRect(runningAnimation.Play())), itself called from the Move() function of the CharacterManager (character->Move(value)). And at this point, all of the Animation object absolutely doesn't look like it should. In debug mode, I can see this :
Debug screenshot
As I said earlier, I think the problem comes from the use of pointers. When I'm trying to remove them, the code runs, but I get a white square problem.
I tried to find some sort of tutorial on how to use this kind of architecture, but didn't find anything relevant. If you know one, I'll be glad to look at it.
As I said earlier, I think the problem comes from the use of pointers.
When I'm trying to remove them, the code runs, but I get a white
square problem.
yep, it is a common issue for SFML when using Texture and Sprite when shallow copy is used.
Let's look at sf::Sprite::setTexture reference:
The texture argument refers to a texture that must exist as long as
the sprite uses it. Indeed, the sprite doesn't store its own copy of
the texture, but rather keeps a pointer to the one that you passed to
this function. If the source texture is destroyed and the sprite tries
to use it, the behavior is undefined.
So, a class like below, with default generated copy operation by compiler:
class Foo {
public:
void setT() {
// generate texture {t}
s.setTexture(t);
}
sf::Sprite s;
sf::Texture t;
};
will bring troubles to you. Because when a copy is made by Foo f(otherFoo);, sprite in newly created instance of Foo will have pointer to texture of otherFoo - it is shallow copy of pointer to sf::Texture. Deleting otherFoo will make a dangle pointer inside new constructed object.
In this case, you should implement assignment operation which makes deep copy of texture for sprite. If you don't know how to do it, you should mark assignment operations as deleted:
class Character : public sf::Drawable {
public:
Character();
Character(std::string name);
~Character();
// added
Character& operator=(const Character&) = delete;
Character(const Character&) = delete;
void Move(float value);
Then, compiler will give you an error for each attempt of copying of Character instance.
In case of deleted copy operation, you should rely on pointers. Your attempt failed, because you store pointer to local variables. Local variables are deleted at the end of a function scope, and referring to them later is undefined behaviour.
You have to create Character by operator new:
void CharacterManager::CreateCharacter(std::string name, std::string textureFilename, CharacterType characterType, sf::Vector2f pos) {
Character* new_character = new Character(name); // Create a generic character...
//...
// Set texture, position and add to the characters list
new_character->SetTexture(textureFilename);
new_character->SetPosition(pos);
m_characters.insert({ name, new_character });
}

Qt resize frameless widget on MacOSX

I need to create a frameless widget in Qt, and it needs to be resizable. The app will run under Windows and Mac OSX.
If I use: setWindowFlags(Qt::FramelessWindowHint);
I can resize the window from the bottom-right corner (a QSizeGrip appears, I guess it's included in QMainWindow widget).
I can add grips at each corner, but I want the window to be resizable from the sides (and not only the corners). Is there any easy way to make it resizable from all sides like a normal window?
There is a workaround for Windows, which consists on override nativeEvent handler: Qt/Windows, resizable frameless window , but I couldn't find a solution for Mac OSX platform.
The best way to do this is to catch the mouse move events and show the cursor appropriate for where you are and do the resizing when the button is held down. There is an example that provides a Frame widget which you can put your own content one into.
#include <QtWidgets>
class Frame : public QFrame
{
public:
Frame()
{
m_mouse_down = false;
setFrameShape(Panel);
// Make this a borderless window which can't
// be resized or moved via the window system
setWindowFlags(Qt::FramelessWindowHint);
setMouseTracking(true);
m_content = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(m_content);
layout->setMargin(5);
layout->setSpacing(0);
setLayout(layout);
}
// Allows you to access the content area of the frame
// where widgets and layouts can be added
QWidget *contentWidget() const { return m_content; }
void mousePressEvent(QMouseEvent *e)
{
m_old_pos = e->pos();
m_mouse_down = e->button() == Qt::LeftButton;
}
void mouseMoveEvent(QMouseEvent *e)
{
int x = e->x();
int y = e->y();
if (m_mouse_down) {
int dx = x - m_old_pos.x();
int dy = y - m_old_pos.y();
QRect g = geometry();
if (left)
g.setLeft(g.left() + dx);
if (right)
g.setRight(g.right() + dx);
if (bottom)
g.setBottom(g.bottom() + dy);
if (top)
g.setTop(g.top() + dy);
setGeometry(g);
m_old_pos = QPoint(!left ? e->x() : m_old_pos.x(), e->y());
} else {
QRect r = rect();
top = qAbs(y - r.top()) <= 5;
left = qAbs(x - r.left()) <= 5;
right = qAbs(x - r.right()) <= 5;
bottom = qAbs(y - r.bottom()) <= 5;
bool hor = left | right;
if (hor && bottom) {
if (left)
setCursor(Qt::SizeBDiagCursor);
else
setCursor(Qt::SizeFDiagCursor);
} else if (hor) {
setCursor(Qt::SizeHorCursor);
} else if (bottom || top) {
setCursor(Qt::SizeVerCursor);
} else {
setCursor(Qt::ArrowCursor);
}
}
}
void mouseReleaseEvent(QMouseEvent *e)
{
m_mouse_down = false;
}
private:
QWidget *m_content;
QPoint m_old_pos;
bool m_mouse_down;
bool left, right, bottom, top;
};
#include "main.moc"
int main(int argc, char **argv)
{
QApplication app(argc, argv);
Frame box;
QVBoxLayout *l = new QVBoxLayout(box.contentWidget());
l->setMargin(0);
QTextEdit *edit = new QTextEdit(box.contentWidget());
l->addWidget(edit);
box.show();
return app.exec();
}

Show grey level value of pixel in Qt when the mouse is over it

Currently I am working on the display of gray level image with zoom feature. I am able to get the position of the pixel and the zoom feature is working well. However I encountered two problems:
1.) How can I get the grey level value of the pixel that is pointed by the mouse? I only managed to obtain the rgb value through “QRgb rgbValue = pix.toImage().pixel(x,y)”. How can I convert it into grey level value? Or is there any direct way to get the grey level value of the pixel.
2.) I have implemented “mouseMoveEvent(QMouseEvent *event)” and “setMouseTracking(true)”. However the function of “mouseMoveEvent(QMouseEvent *event)” is not functioning when I move the mouse. What is wrong with my code?
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QGraphicsScene>
#include <QGraphicsItem>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void on_pushButton_clicked();
protected:
void mouseMoveEvent(QMouseEvent * event);
private:
Ui::MainWindow *ui;
QGraphicsScene* scene;
QGraphicsItem* item;
QPixmap pix;
};
#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);
QImage image("E:/image_00002.bmp");
pix = QPixmap::fromImage(image);
scene = new QGraphicsScene(this);
ui->graphicsView->setScene(scene);
scene->addPixmap(pix);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
ui->graphicsView->setMouseTracking(true);
}
void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
QPoint local_pt = ui->graphicsView->mapFromGlobal(event->globalPos());
QPointF img_coord_pt = ui->graphicsView->mapToScene(local_pt);
double x = img_coord_pt.x();
double y = img_coord_pt.y();
/* How can I get a gray level image here */
QRgb rgbValue = pix.toImage().pixel(x,y);
ui->label_X->setText(QString::number(x));
ui->label_Y->setText(QString::number(y));
ui->label_Value->setText(QString::number(rgbValue));
}
To elaborate a bit on what hank said. In order for your QMainWindow to receive the events from the QGraphicsScene you need to install an event filter (see http://qt-project.org/doc/qt-4.8/qobject.html#installEventFilter ). Quoting from the DOCs:
An event filter is an object that receives all events that are sent to this object.
The filter can either stop the event or forward it to this object.
In order to process the events you need to define an eventFilter method in your main window class. Below are the on_pushButton_clicked() and eventFilter() methods that should do what you want:
void MainWindow::on_pushButton_clicked()
{
ui->graphicsView->setMouseTracking(true);
scene->installEventFilter(this);
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::GraphicsSceneMouseMove ) {
QGraphicsSceneMouseEvent *mouseEvent = static_cast<QGraphicsSceneMouseEvent*>(event);
QPointF img_coord_pt = mouseEvent->scenePos();
double x = img_coord_pt.x();
double y = img_coord_pt.y();
QColor color = QColor(pix.toImage().pixel(x,y));
int average = (color.red()+color.green()+color.blue())/3;
ui->label_X->setText(QString::number(x));
ui->label_Y->setText(QString::number(y));
ui->label_Value->setText(QString::number(average));
return true;
} else {
return QObject::eventFilter(obj, event);
}
}
Does this help?

QGraphicsScene/QGraphicsView performance

I am using QGraphicsScene/QGraphicsView pair in my project
I have performance issue with this pair.
I added my custom graphics items to scene and displayed the contents with view. After that my custom graphics items paint method continuously called by scene(just like infinite loop). This makes %25 of CPU usage(approximately 400 items on scene). What may cause this behaviour?
Here is one my item implementation:
class LevelCrossingItem : public QGraphicsWidget
{
public:
LevelCrossingItem(QString _id,qreal _x,qreal _y);
~LevelCrossingItem();
QRectF boundingRect() const;
QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget /* = 0 */);
void readStateBits();
bool isClosed();
bool isGateArmBroken();
bool isOpenedDuringRouteTanzimCompleted();
bool hasDataConsistencyWarning();
int type() const {return Type;}
private slots:
void setVisible(bool);
private:
enum {Type = FIELDLEVELCROSSING};
QString m_id;
QString m_source;
short m_closedState;
short m_brokenGateArmState;
short m_openedDuringRouteTanzimCompletedState;
short m_dataConsistencyWarningState;
QBitArray stateBitArray;
qreal x,y;
QSvgRenderer *renderer;
};
#include "levelcrossing.h"
LevelCrossingItem::LevelCrossingItem(QString _id,qreal _x,qreal _y):m_id(_id),x(_x),y(_y),stateBitArray(4)
{
m_source = LEVELCROSSING_RESOURCE_PATH.arg("open");
renderer = new QSvgRenderer;
setStateArray(stateBitArray);
setZValue(-0.5);
}
LevelCrossingItem::~LevelCrossingItem()
{
delete renderer;
}
void LevelCrossingItem::setVisible(bool visible)
{
QGraphicsItem::setVisible(visible);
}
QRectF LevelCrossingItem::boundingRect() const
{
return QRectF(QPointF(x,y),sizeHint(Qt::PreferredSize));
}
QSizeF LevelCrossingItem::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
{
return QSizeF(50,270);
}
void LevelCrossingItem::readStateBits()
{
m_closedState = property("Closed").toInt();
m_brokenGateArmState = property("Broken").toInt();
m_openedDuringRouteTanzimCompletedState = property("OpenedOnRouteWarning").toInt();
m_dataConsistencyWarningState = property("DataConsistencyWarning").toInt();
stateBitArray.setBit(0,qvariant_cast<bool>(m_closedState));
stateBitArray.setBit(1,qvariant_cast<bool>(m_brokenGateArmState));
stateBitArray.setBit(2,qvariant_cast<bool>(m_openedDuringRouteTanzimCompletedState));
stateBitArray.setBit(3,qvariant_cast<bool>(m_dataConsistencyWarningState));
setStateArray(stateBitArray);
}
void LevelCrossingItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget )
{
Q_UNUSED(option);
Q_UNUSED(widget);
readStateBits();
m_closedState == Positive ? m_source = LEVELCROSSING_RESOURCE_PATH.arg("closed")
: m_source = LEVELCROSSING_RESOURCE_PATH.arg("open");
m_brokenGateArmState == Positive ? m_source = LEVELCROSSING_RESOURCE_PATH.arg("broken")
: m_source = m_source;
if(m_openedDuringRouteTanzimCompletedState == Positive)
{
setWarningVisible(OOR_WRN.arg(name()).arg(interlockingRegionId()),true);
if(stateChanged())
emit itemAlarmOccured(m_id,LevelCrossingIsOpenDuringTanzimCompleted);
}
else
setWarningVisible(OOR_WRN.arg(name()).arg(interlockingRegionId()),false);
if(m_dataConsistencyWarningState == Positive)
{
setWarningVisible(DC_WRN.arg(name()).arg(interlockingRegionId()),true);
if(stateChanged())
emit itemAlarmOccured(m_id,LevelCrossingDataConsistency);
}
else
setWarningVisible(DC_WRN.arg(name()).arg(interlockingRegionId()),false);
renderer->load(m_source);
renderer->render(painter,boundingRect());
}
bool LevelCrossingItem::isClosed()
{
return m_closedState == Positive;
}
bool LevelCrossingItem::isGateArmBroken()
{
return m_brokenGateArmState == Positive;
}
bool LevelCrossingItem::isOpenedDuringRouteTanzimCompleted()
{
return m_openedDuringRouteTanzimCompletedState == Positive;
}
bool LevelCrossingItem::hasDataConsistencyWarning()
{
return m_dataConsistencyWarningState == Positive;
}
I read x and y coordinates from xml file. For this item x and y coordinates are 239,344 respectively
Most likely your graphics item has mistakes in the implementation. I had similar behavior, but I was unable to figure out what exactly causes it. I suspect that this happens when you draw outside of the bounding rect. This triggers some clean up routine, which in turn causes redraw of the item, and here goes the loop. Eventually I resolved the issue by carefully inspecting the implementation of my custom graphics item and making sure that:
These is no painting outside of the MyGraphicsItem::boundingRect. Use painter->setClipRect(boundingRect())
MyGraphicsItem::shape does not cross with the MyGraphicsItem::boundingRect.
If you override any other QGraphicsItem functions, make sure that your implementation is correct.
Hope this helps. Feel free to post the source code of your graphics item, it will be easier to find the problem.

Resources